feat: 初始化知识库系统项目

- 实现基于 Laravel 11 和 Filament 3.X 的文档管理系统
- 添加用户认证和分组管理功能
- 实现文档上传、分类和权限控制
- 集成 Word 文档自动转换为 Markdown
- 集成 Meilisearch 全文搜索引擎
- 实现文档在线预览功能
- 添加安全日志和审计功能
- 完整的简体中文界面
- 包含完整的项目文档和部署指南

技术栈:
- Laravel 11.x
- Filament 3.X
- Meilisearch 1.5+
- Pandoc 文档转换
- Redis 队列系统
- Pest PHP 测试框架
This commit is contained in:
Knowledge Base System
2025-12-05 14:44:44 +08:00
commit acf549c43c
165 changed files with 32838 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
<?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(),
]);
// 不抛出异常,避免影响删除操作
}
}
}