- 实现基于 Laravel 11 和 Filament 3.X 的文档管理系统 - 添加用户认证和分组管理功能 - 实现文档上传、分类和权限控制 - 集成 Word 文档自动转换为 Markdown - 集成 Meilisearch 全文搜索引擎 - 实现文档在线预览功能 - 添加安全日志和审计功能 - 完整的简体中文界面 - 包含完整的项目文档和部署指南 技术栈: - Laravel 11.x - Filament 3.X - Meilisearch 1.5+ - Pandoc 文档转换 - Redis 队列系统 - Pest PHP 测试框架
15 KiB
API 参考文档
本文档描述知识库系统的核心服务类和方法。
目录
- DocumentService
- DocumentConversionService
- DocumentSearchService
- MarkdownRenderService
- SecurityLogger
DocumentService
文档管理服务,处理文档上传、下载和权限验证。
位置: app/Services/DocumentService.php
方法
uploadDocument()
上传并保存文档。
public function uploadDocument(
UploadedFile $file,
string $title,
string $type,
?int $groupId,
int $uploaderId,
?string $description = null
): Document
参数:
$file- 上传的文件对象$title- 文档标题$type- 文档类型('global' 或 'dedicated')$groupId- 分组 ID(专用文档必填)$uploaderId- 上传者用户 ID$description- 文档描述(可选)
返回: Document - 创建的文档模型实例
异常:
ValidationException- 文件格式无效或专用文档未指定分组Exception- 文件存储失败
示例:
use App\Services\DocumentService;
use Illuminate\Http\UploadedFile;
$service = app(DocumentService::class);
$document = $service->uploadDocument(
file: $request->file('document'),
title: '技术文档',
type: 'global',
groupId: null,
uploaderId: auth()->id(),
description: '系统架构说明'
);
validateDocumentAccess()
验证用户是否有权访问文档。
public function validateDocumentAccess(Document $document, User $user): bool
参数:
$document- 文档实例$user- 用户实例
返回: bool - 是否有权访问
示例:
$hasAccess = $service->validateDocumentAccess($document, auth()->user());
if (!$hasAccess) {
abort(403, '您没有权限访问此文档');
}
downloadDocument()
下载文档并记录日志。
public function downloadDocument(Document $document, User $user): StreamedResponse
参数:
$document- 文档实例$user- 用户实例
返回: StreamedResponse - 文件流式响应
异常:
AuthorizationException- 用户无权下载FileNotFoundException- 文件不存在
示例:
return $service->downloadDocument($document, auth()->user());
logDownload()
记录文档下载日志。
public function logDownload(Document $document, User $user): void
参数:
$document- 文档实例$user- 用户实例
示例:
$service->logDownload($document, auth()->user());
DocumentConversionService
文档格式转换服务,将 Word 文档转换为 Markdown。
位置: app/Services/DocumentConversionService.php
方法
convertToMarkdown()
将 Word 文档转换为 Markdown 格式。
public function convertToMarkdown(Document $document): string
参数:
$document- 文档实例
返回: string - Markdown 内容
异常:
ConversionException- 转换失败
示例:
use App/Services/DocumentConversionService;
$service = app(DocumentConversionService::class);
$markdown = $service->convertToMarkdown($document);
queueConversion()
将文档转换任务加入队列。
public function queueConversion(Document $document): void
参数:
$document- 文档实例
示例:
$service->queueConversion($document);
saveMarkdownToFile()
保存 Markdown 内容到文件。
public function saveMarkdownToFile(Document $document, string $markdown): string
参数:
$document- 文档实例$markdown- Markdown 内容
返回: string - 文件存储路径
示例:
$path = $service->saveMarkdownToFile($document, $markdown);
updateDocumentMarkdown()
更新文档的 Markdown 相关字段。
public function updateDocumentMarkdown(Document $document, string $markdownPath): void
参数:
$document- 文档实例$markdownPath- Markdown 文件路径
示例:
$service->updateDocumentMarkdown($document, $markdownPath);
handleConversionFailure()
处理转换失败的情况。
public function handleConversionFailure(Document $document, Exception $e): void
参数:
$document- 文档实例$e- 异常对象
示例:
try {
$markdown = $service->convertToMarkdown($document);
} catch (Exception $e) {
$service->handleConversionFailure($document, $e);
}
getMarkdownPreview()
获取 Markdown 内容摘要。
public function getMarkdownPreview(string $markdown, int $length = 500): string
参数:
$markdown- Markdown 内容$length- 摘要长度(默认 500 字符)
返回: string - Markdown 摘要
示例:
$preview = $service->getMarkdownPreview($markdown, 200);
DocumentSearchService
文档搜索服务,提供全文搜索和权限过滤。
位置: app/Services/DocumentSearchService.php
方法
search()
搜索文档。
public function search(string $query, User $user, array $filters = []): Collection
参数:
$query- 搜索关键词$user- 用户实例$filters- 筛选条件数组(可选)type: 文档类型('global' 或 'dedicated')group_id: 分组 ID
返回: Collection - 搜索结果集合
示例:
use App\Services\DocumentSearchService;
$service = app(DocumentSearchService::class);
$results = $service->search(
query: '技术文档',
user: auth()->user(),
filters: [
'type' => 'global',
]
);
indexDocument()
将文档索引到 Meilisearch。
public function indexDocument(Document $document): void
参数:
$document- 文档实例
示例:
$service->indexDocument($document);
updateDocumentIndex()
更新文档索引。
public function updateDocumentIndex(Document $document): void
参数:
$document- 文档实例
示例:
$service->updateDocumentIndex($document);
removeDocumentFromIndex()
从索引中删除文档。
public function removeDocumentFromIndex(Document $document): void
参数:
$document- 文档实例
示例:
$service->removeDocumentFromIndex($document);
filterByUserPermissions()
根据用户权限过滤搜索结果。
public function filterByUserPermissions(Collection $results, User $user): Collection
参数:
$results- 搜索结果集合$user- 用户实例
返回: Collection - 过滤后的结果集合
示例:
$filteredResults = $service->filterByUserPermissions($results, auth()->user());
prepareSearchableData()
准备文档的可搜索数据。
public function prepareSearchableData(Document $document): array
参数:
$document- 文档实例
返回: array - 可搜索数据数组
示例:
$searchableData = $service->prepareSearchableData($document);
MarkdownRenderService
Markdown 渲染服务,将 Markdown 转换为 HTML。
位置: app/Services/MarkdownRenderService.php
方法
render()
将 Markdown 渲染为 HTML。
public function render(string $markdown): string
参数:
$markdown- Markdown 内容
返回: string - 渲染后的 HTML
示例:
use App\Services\MarkdownRenderService;
$service = app(MarkdownRenderService::class);
$html = $service->render($markdown);
sanitize()
清理 HTML 内容,防止 XSS 攻击。
public function sanitize(string $html): string
参数:
$html- HTML 内容
返回: string - 清理后的 HTML
示例:
$safeHtml = $service->sanitize($html);
extractPreview()
从 Markdown 中提取摘要。
public function extractPreview(string $markdown, int $length = 200): string
参数:
$markdown- Markdown 内容$length- 摘要长度(默认 200 字符)
返回: string - 摘要文本
示例:
$preview = $service->extractPreview($markdown, 150);
SecurityLogger
安全日志记录服务。
位置: app/Services/SecurityLogger.php
方法
logUnauthorizedAccess()
记录未授权访问尝试。
public function logUnauthorizedAccess(
User $user,
Document $document,
string $action
): void
参数:
$user- 用户实例$document- 文档实例$action- 尝试的操作(如 'view', 'download')
示例:
use App\Services\SecurityLogger;
$logger = app(SecurityLogger::class);
$logger->logUnauthorizedAccess(
user: auth()->user(),
document: $document,
action: 'download'
);
logDocumentAccess()
记录文档访问。
public function logDocumentAccess(
User $user,
Document $document,
string $action
): void
参数:
$user- 用户实例$document- 文档实例$action- 操作类型
示例:
$logger->logDocumentAccess(
user: auth()->user(),
document: $document,
action: 'view'
);
模型查询作用域
Document 模型
accessibleBy()
获取用户可访问的文档。
Document::accessibleBy($user)->get();
参数:
$user- 用户实例
返回: 查询构建器
示例:
// 获取用户可访问的所有文档
$documents = Document::accessibleBy(auth()->user())->get();
// 结合其他查询条件
$recentDocuments = Document::accessibleBy(auth()->user())
->where('created_at', '>=', now()->subDays(7))
->orderBy('created_at', 'desc')
->get();
global()
获取全局文档。
Document::global()->get();
返回: 查询构建器
示例:
$globalDocuments = Document::global()->get();
dedicated()
获取专用文档。
Document::dedicated()->get();
返回: 查询构建器
示例:
$dedicatedDocuments = Document::dedicated()
->where('group_id', $groupId)
->get();
策略方法
DocumentPolicy
view()
检查用户是否可以查看文档。
Gate::allows('view', $document);
参数:
$document- 文档实例
返回: bool
示例:
if (Gate::allows('view', $document)) {
// 用户可以查看文档
}
// 或使用 authorize 方法
$this->authorize('view', $document);
download()
检查用户是否可以下载文档。
Gate::allows('download', $document);
参数:
$document- 文档实例
返回: bool
示例:
if (Gate::denies('download', $document)) {
abort(403, '您没有权限下载此文档');
}
队列任务
ConvertDocumentToMarkdown
文档转换队列任务。
位置: app/Jobs/ConvertDocumentToMarkdown.php
分发任务:
use App\Jobs\ConvertDocumentToMarkdown;
ConvertDocumentToMarkdown::dispatch($document);
延迟分发:
ConvertDocumentToMarkdown::dispatch($document)
->delay(now()->addMinutes(5));
指定队列:
ConvertDocumentToMarkdown::dispatch($document)
->onQueue('documents');
事件和监听器
文档事件
系统会在文档生命周期的关键点触发事件:
文档创建后
// 自动触发转换和索引
Document::created(function ($document) {
// 队列转换任务
// 索引到 Meilisearch
});
文档更新后
Document::updated(function ($document) {
// 如果文件变更,重新转换
// 更新 Meilisearch 索引
});
文档删除后
Document::deleted(function ($document) {
// 删除文件
// 从 Meilisearch 移除索引
});
配置选项
文档转换配置
文件: config/documents.php
return [
'conversion' => [
'driver' => env('DOCUMENT_CONVERSION_DRIVER', 'pandoc'),
'pandoc_path' => env('PANDOC_PATH', '/usr/bin/pandoc'),
'timeout' => env('CONVERSION_TIMEOUT', 300),
'queue' => 'documents',
],
'markdown' => [
'renderer' => 'commonmark',
'sanitize' => true,
],
];
Scout 配置
文件: config/scout.php
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://127.0.0.1:7700'),
'key' => env('MEILISEARCH_KEY'),
'index-settings' => [
'documents' => [
'filterableAttributes' => ['type', 'group_id', 'uploaded_by'],
'sortableAttributes' => ['created_at', 'title'],
'searchableAttributes' => ['title', 'description', 'markdown_content'],
],
],
],
错误处理
常见异常
ValidationException
文件格式验证失败或必填字段缺失。
try {
$document = $service->uploadDocument(...);
} catch (ValidationException $e) {
return back()->withErrors($e->errors());
}
AuthorizationException
用户无权执行操作。
try {
$this->authorize('download', $document);
} catch (AuthorizationException $e) {
abort(403, '您没有权限下载此文档');
}
FileNotFoundException
文件不存在。
try {
return $service->downloadDocument($document, $user);
} catch (FileNotFoundException $e) {
abort(404, '文件不存在');
}
ConversionException
文档转换失败。
try {
$markdown = $service->convertToMarkdown($document);
} catch (ConversionException $e) {
Log::error('文档转换失败', [
'document_id' => $document->id,
'error' => $e->getMessage(),
]);
}
最佳实践
1. 使用依赖注入
class DocumentController extends Controller
{
public function __construct(
private DocumentService $documentService,
private MarkdownRenderService $markdownService
) {}
public function preview(Document $document)
{
$this->authorize('view', $document);
$markdown = $document->getMarkdownContent();
$html = $this->markdownService->render($markdown);
return view('documents.preview', compact('html', 'document'));
}
}
2. 使用事务处理
DB::transaction(function () use ($file, $data) {
$document = $this->documentService->uploadDocument(
file: $file,
title: $data['title'],
type: $data['type'],
groupId: $data['group_id'] ?? null,
uploaderId: auth()->id()
);
// 其他相关操作
});
3. 使用查询作用域
// 好的做法
$documents = Document::accessibleBy(auth()->user())
->where('type', 'global')
->latest()
->paginate(20);
// 避免手动实现权限过滤
4. 异步处理耗时操作
// 文档转换应该异步处理
ConvertDocumentToMarkdown::dispatch($document);
// 而不是同步执行
// $service->convertToMarkdown($document); // 避免
最后更新:2025-12-05