*/ protected $fillable = [ 'title', 'file_path', 'file_name', 'file_size', 'mime_type', 'type', 'group_id', 'uploaded_by', 'description', 'markdown_path', 'markdown_preview', 'conversion_status', 'conversion_error', ]; /** * 获取文档所属的分组 */ public function group(): BelongsTo { return $this->belongsTo(Group::class); } /** * 获取文档的上传者 */ public function uploader(): BelongsTo { return $this->belongsTo(User::class, 'uploaded_by'); } /** * 获取文档的所有下载日志 */ public function downloadLogs(): HasMany { return $this->hasMany(DownloadLog::class); } /** * 查询作用域:获取用户可访问的文档 * 包含全局文档和用户分组的专用文档,排除其他分组的专用文档 * * @param Builder $query * @param User $user * @return Builder */ public function scopeAccessibleBy(Builder $query, User $user): Builder { // 获取用户所属的所有分组 ID $userGroupIds = $user->groups()->pluck('groups.id')->toArray(); return $query->where(function (Builder $query) use ($userGroupIds) { // 包含所有全局文档 $query->where('type', 'global') // 或者包含用户所属分组的专用文档 ->orWhere(function (Builder $query) use ($userGroupIds) { $query->where('type', 'dedicated') ->whereIn('group_id', $userGroupIds); }); }); } /** * 查询作用域:仅获取全局文档 * * @param Builder $query * @return Builder */ public function scopeGlobal(Builder $query): Builder { return $query->where('type', 'global'); } /** * 查询作用域:仅获取专用文档 * * @param Builder $query * @return Builder */ public function scopeDedicated(Builder $query): Builder { return $query->where('type', 'dedicated'); } /** * 获取可搜索的数组数据 * 用于 Meilisearch 索引 * * @return array */ public function toSearchableArray(): array { return [ 'id' => $this->id, 'title' => $this->title, 'file_name' => $this->file_name, 'description' => $this->description, 'markdown_content' => $this->getMarkdownContent(), 'type' => $this->type, 'group_id' => $this->group_id, 'uploaded_by' => $this->uploaded_by, 'created_at' => $this->created_at?->timestamp, ]; } /** * 判断文档是否应该被索引 * 只有转换完成的文档才会被索引 * * @return bool */ public function shouldBeSearchable(): bool { return $this->conversion_status === 'completed'; } /** * 获取完整的 Markdown 内容 * 从文件系统读取 Markdown 文件 * * @return string|null */ 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 * * @return bool */ public function hasMarkdown(): bool { return !empty($this->markdown_path) && $this->conversion_status === 'completed'; } }