148 lines
3.9 KiB
PHP
148 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Laravel\Scout\Searchable;
|
|
|
|
class Document extends Model
|
|
{
|
|
use HasFactory, Searchable;
|
|
/**
|
|
* 可批量赋值的属性
|
|
*
|
|
* @var array<int, string>
|
|
*/
|
|
protected $fillable = [
|
|
'title',
|
|
'file_path',
|
|
'file_name',
|
|
'file_size',
|
|
'mime_type',
|
|
'uploaded_by',
|
|
'description',
|
|
'markdown_path',
|
|
'conversion_status',
|
|
'conversion_error',
|
|
'knowledge_base_id',
|
|
];
|
|
|
|
/**
|
|
* 获取文档所属的知识库
|
|
*/
|
|
public function knowledgeBase(): BelongsTo
|
|
{
|
|
return $this->belongsTo(KnowledgeBase::class);
|
|
}
|
|
|
|
/**
|
|
* 获取文档的上传者
|
|
*/
|
|
public function uploader(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'uploaded_by');
|
|
}
|
|
|
|
/**
|
|
* 获取文档的所有下载日志
|
|
*/
|
|
public function downloadLogs(): HasMany
|
|
{
|
|
return $this->hasMany(DownloadLog::class);
|
|
}
|
|
|
|
/**
|
|
* 获取可搜索的数组数据
|
|
* 用于 Meilisearch 索引
|
|
*/
|
|
public function toSearchableArray(): array
|
|
{
|
|
return [
|
|
'id' => $this->id,
|
|
'title' => $this->title,
|
|
'file_name' => $this->file_name,
|
|
'description' => $this->description,
|
|
'markdown_content' => $this->getMarkdownContent(),
|
|
'knowledge_base_id' => $this->knowledge_base_id,
|
|
'uploaded_by' => $this->uploaded_by,
|
|
'created_at' => $this->created_at?->timestamp,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 判断文档是否应该被索引
|
|
* 只有转换完成的文档才会被索引
|
|
*/
|
|
public function shouldBeSearchable(): bool
|
|
{
|
|
return $this->conversion_status === 'completed';
|
|
}
|
|
|
|
/**
|
|
* 获取完整的 Markdown 内容
|
|
* 从文件系统读取 Markdown 文件
|
|
*/
|
|
public function getMarkdownContent(): ?string
|
|
{
|
|
if (!$this->markdown_path) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
if (Storage::disk('markdown')->exists($this->markdown_path)) {
|
|
return Storage::disk('markdown')->get($this->markdown_path);
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::warning('Failed to read markdown content', [
|
|
'document_id' => $this->id,
|
|
'markdown_path' => $this->markdown_path,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 检查文档是否已转换为 Markdown
|
|
*/
|
|
public function hasMarkdown(): bool
|
|
{
|
|
return !empty($this->markdown_path) && $this->conversion_status === 'completed';
|
|
}
|
|
|
|
/**
|
|
* 获取用于展示和下载的文件名
|
|
* 对历史上误保存为随机存储名的记录回退到“标题.扩展名”
|
|
*/
|
|
public function getDisplayFileNameAttribute(): string
|
|
{
|
|
$fileName = trim((string) $this->file_name);
|
|
|
|
if ($fileName !== '' && ! $this->looksLikeGeneratedStorageName($fileName)) {
|
|
return $fileName;
|
|
}
|
|
|
|
$extension = pathinfo($fileName ?: $this->file_path, PATHINFO_EXTENSION);
|
|
$title = trim((string) $this->title);
|
|
$title = preg_replace('/[<>:"\/\\\\|?*\x00-\x1F]+/u', '-', $title) ?? '';
|
|
$title = trim($title, " .-\t\n\r\0\x0B");
|
|
$title = $title !== '' ? $title : 'document';
|
|
|
|
return $extension !== '' ? "{$title}.{$extension}" : $title;
|
|
}
|
|
|
|
protected function looksLikeGeneratedStorageName(string $fileName): bool
|
|
{
|
|
$baseName = pathinfo($fileName, PATHINFO_FILENAME);
|
|
|
|
return Str::isUuid($baseName)
|
|
|| (bool) preg_match('/^[0-9A-HJKMNP-TV-Z]{26}$/i', $baseName);
|
|
}
|
|
}
|