- 实现基于 Laravel 11 和 Filament 3.X 的文档管理系统 - 添加用户认证和分组管理功能 - 实现文档上传、分类和权限控制 - 集成 Word 文档自动转换为 Markdown - 集成 Meilisearch 全文搜索引擎 - 实现文档在线预览功能 - 添加安全日志和审计功能 - 完整的简体中文界面 - 包含完整的项目文档和部署指南 技术栈: - Laravel 11.x - Filament 3.X - Meilisearch 1.5+ - Pandoc 文档转换 - Redis 队列系统 - Pest PHP 测试框架
136 lines
4.2 KiB
PHP
136 lines
4.2 KiB
PHP
<?php
|
||
|
||
namespace App\Observers;
|
||
|
||
use App\Models\Document;
|
||
use App\Services\DocumentSearchService;
|
||
|
||
/**
|
||
* 文档观察者
|
||
* 监听文档模型事件,自动管理 Meilisearch 索引
|
||
*/
|
||
class DocumentObserver
|
||
{
|
||
protected DocumentSearchService $searchService;
|
||
|
||
public function __construct(DocumentSearchService $searchService)
|
||
{
|
||
$this->searchService = $searchService;
|
||
}
|
||
|
||
/**
|
||
* 处理文档 "created" 事件
|
||
* 注意:文档创建时不立即索引,等待转换完成后再索引
|
||
*/
|
||
public function created(Document $document): void
|
||
{
|
||
// 文档创建时不立即索引,因为 Markdown 内容还未生成
|
||
// 索引将在转换完成后通过 updated 事件触发
|
||
}
|
||
|
||
/**
|
||
* 处理文档 "updated" 事件
|
||
* 当文档更新时,检查转换状态并更新索引
|
||
*/
|
||
public function updated(Document $document): void
|
||
{
|
||
// 检查转换状态是否变为 completed
|
||
if ($document->wasChanged('conversion_status') && $document->conversion_status === 'completed') {
|
||
// 转换完成,创建或更新索引
|
||
$this->searchService->indexDocument($document);
|
||
} elseif ($document->wasChanged(['title', 'description', 'markdown_path', 'type', 'group_id'])) {
|
||
// 其他重要字段更新时,也更新索引
|
||
$this->searchService->updateDocumentIndex($document);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理文档 "deleting" 事件
|
||
* 在删除前清理相关文件
|
||
*/
|
||
public function deleting(Document $document): void
|
||
{
|
||
$this->cleanupDocumentFiles($document);
|
||
}
|
||
|
||
/**
|
||
* 处理文档 "deleted" 事件
|
||
* 从 Meilisearch 中移除文档索引
|
||
*/
|
||
public function deleted(Document $document): void
|
||
{
|
||
$this->searchService->removeDocumentFromIndex($document);
|
||
}
|
||
|
||
/**
|
||
* 处理文档 "restored" 事件
|
||
* 恢复文档时重新索引
|
||
*/
|
||
public function restored(Document $document): void
|
||
{
|
||
if ($document->shouldBeSearchable()) {
|
||
$this->searchService->indexDocument($document);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理文档 "force deleting" 事件
|
||
* 在强制删除前清理相关文件
|
||
*/
|
||
public function forceDeleting(Document $document): void
|
||
{
|
||
$this->cleanupDocumentFiles($document);
|
||
}
|
||
|
||
/**
|
||
* 处理文档 "force deleted" 事件
|
||
* 强制删除时也要移除索引
|
||
*/
|
||
public function forceDeleted(Document $document): void
|
||
{
|
||
$this->searchService->removeDocumentFromIndex($document);
|
||
}
|
||
|
||
/**
|
||
* 清理文档相关的所有文件
|
||
* 包括原始文档文件、Markdown 文件和媒体文件
|
||
*
|
||
* @param Document $document
|
||
* @return void
|
||
*/
|
||
protected function cleanupDocumentFiles(Document $document): void
|
||
{
|
||
try {
|
||
// 删除原始文档文件
|
||
if ($document->file_path && \Storage::disk('local')->exists($document->file_path)) {
|
||
\Storage::disk('local')->delete($document->file_path);
|
||
\Log::info('已删除原始文档文件', [
|
||
'document_id' => $document->id,
|
||
'file_path' => $document->file_path,
|
||
]);
|
||
}
|
||
|
||
// 删除 Markdown 文件和整个文档目录(包括 media)
|
||
if ($document->markdown_path) {
|
||
// 获取文档目录(例如:2025/12/04/{uuid})
|
||
$documentDir = dirname($document->markdown_path);
|
||
|
||
// 删除整个文档目录(包括 Markdown 文件和 media 目录)
|
||
if (\Storage::disk('markdown')->exists($documentDir)) {
|
||
\Storage::disk('markdown')->deleteDirectory($documentDir);
|
||
\Log::info('已删除文档目录', [
|
||
'document_id' => $document->id,
|
||
'directory' => $documentDir,
|
||
]);
|
||
}
|
||
}
|
||
} catch (\Exception $e) {
|
||
\Log::error('清理文档文件失败', [
|
||
'document_id' => $document->id,
|
||
'error' => $e->getMessage(),
|
||
]);
|
||
// 不抛出异常,避免影响删除操作
|
||
}
|
||
}
|
||
}
|