Files
KnowledgeBase/app/Models/Document.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);
}
}