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

852
docs/API_REFERENCE.md Normal file
View File

@@ -0,0 +1,852 @@
# API 参考文档
本文档描述知识库系统的核心服务类和方法。
## 目录
1. [DocumentService](#documentservice)
2. [DocumentConversionService](#documentconversionservice)
3. [DocumentSearchService](#documentsearchservice)
4. [MarkdownRenderService](#markdownrenderservice)
5. [SecurityLogger](#securitylogger)
---
## DocumentService
文档管理服务,处理文档上传、下载和权限验证。
**位置**: `app/Services/DocumentService.php`
### 方法
#### uploadDocument()
上传并保存文档。
```php
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` - 文件存储失败
**示例**:
```php
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()
验证用户是否有权访问文档。
```php
public function validateDocumentAccess(Document $document, User $user): bool
```
**参数**:
- `$document` - 文档实例
- `$user` - 用户实例
**返回**: `bool` - 是否有权访问
**示例**:
```php
$hasAccess = $service->validateDocumentAccess($document, auth()->user());
if (!$hasAccess) {
abort(403, '您没有权限访问此文档');
}
```
#### downloadDocument()
下载文档并记录日志。
```php
public function downloadDocument(Document $document, User $user): StreamedResponse
```
**参数**:
- `$document` - 文档实例
- `$user` - 用户实例
**返回**: `StreamedResponse` - 文件流式响应
**异常**:
- `AuthorizationException` - 用户无权下载
- `FileNotFoundException` - 文件不存在
**示例**:
```php
return $service->downloadDocument($document, auth()->user());
```
#### logDownload()
记录文档下载日志。
```php
public function logDownload(Document $document, User $user): void
```
**参数**:
- `$document` - 文档实例
- `$user` - 用户实例
**示例**:
```php
$service->logDownload($document, auth()->user());
```
---
## DocumentConversionService
文档格式转换服务,将 Word 文档转换为 Markdown。
**位置**: `app/Services/DocumentConversionService.php`
### 方法
#### convertToMarkdown()
将 Word 文档转换为 Markdown 格式。
```php
public function convertToMarkdown(Document $document): string
```
**参数**:
- `$document` - 文档实例
**返回**: `string` - Markdown 内容
**异常**:
- `ConversionException` - 转换失败
**示例**:
```php
use App/Services/DocumentConversionService;
$service = app(DocumentConversionService::class);
$markdown = $service->convertToMarkdown($document);
```
#### queueConversion()
将文档转换任务加入队列。
```php
public function queueConversion(Document $document): void
```
**参数**:
- `$document` - 文档实例
**示例**:
```php
$service->queueConversion($document);
```
#### saveMarkdownToFile()
保存 Markdown 内容到文件。
```php
public function saveMarkdownToFile(Document $document, string $markdown): string
```
**参数**:
- `$document` - 文档实例
- `$markdown` - Markdown 内容
**返回**: `string` - 文件存储路径
**示例**:
```php
$path = $service->saveMarkdownToFile($document, $markdown);
```
#### updateDocumentMarkdown()
更新文档的 Markdown 相关字段。
```php
public function updateDocumentMarkdown(Document $document, string $markdownPath): void
```
**参数**:
- `$document` - 文档实例
- `$markdownPath` - Markdown 文件路径
**示例**:
```php
$service->updateDocumentMarkdown($document, $markdownPath);
```
#### handleConversionFailure()
处理转换失败的情况。
```php
public function handleConversionFailure(Document $document, Exception $e): void
```
**参数**:
- `$document` - 文档实例
- `$e` - 异常对象
**示例**:
```php
try {
$markdown = $service->convertToMarkdown($document);
} catch (Exception $e) {
$service->handleConversionFailure($document, $e);
}
```
#### getMarkdownPreview()
获取 Markdown 内容摘要。
```php
public function getMarkdownPreview(string $markdown, int $length = 500): string
```
**参数**:
- `$markdown` - Markdown 内容
- `$length` - 摘要长度(默认 500 字符)
**返回**: `string` - Markdown 摘要
**示例**:
```php
$preview = $service->getMarkdownPreview($markdown, 200);
```
---
## DocumentSearchService
文档搜索服务,提供全文搜索和权限过滤。
**位置**: `app/Services/DocumentSearchService.php`
### 方法
#### search()
搜索文档。
```php
public function search(string $query, User $user, array $filters = []): Collection
```
**参数**:
- `$query` - 搜索关键词
- `$user` - 用户实例
- `$filters` - 筛选条件数组(可选)
- `type`: 文档类型('global' 或 'dedicated'
- `group_id`: 分组 ID
**返回**: `Collection` - 搜索结果集合
**示例**:
```php
use App\Services\DocumentSearchService;
$service = app(DocumentSearchService::class);
$results = $service->search(
query: '技术文档',
user: auth()->user(),
filters: [
'type' => 'global',
]
);
```
#### indexDocument()
将文档索引到 Meilisearch。
```php
public function indexDocument(Document $document): void
```
**参数**:
- `$document` - 文档实例
**示例**:
```php
$service->indexDocument($document);
```
#### updateDocumentIndex()
更新文档索引。
```php
public function updateDocumentIndex(Document $document): void
```
**参数**:
- `$document` - 文档实例
**示例**:
```php
$service->updateDocumentIndex($document);
```
#### removeDocumentFromIndex()
从索引中删除文档。
```php
public function removeDocumentFromIndex(Document $document): void
```
**参数**:
- `$document` - 文档实例
**示例**:
```php
$service->removeDocumentFromIndex($document);
```
#### filterByUserPermissions()
根据用户权限过滤搜索结果。
```php
public function filterByUserPermissions(Collection $results, User $user): Collection
```
**参数**:
- `$results` - 搜索结果集合
- `$user` - 用户实例
**返回**: `Collection` - 过滤后的结果集合
**示例**:
```php
$filteredResults = $service->filterByUserPermissions($results, auth()->user());
```
#### prepareSearchableData()
准备文档的可搜索数据。
```php
public function prepareSearchableData(Document $document): array
```
**参数**:
- `$document` - 文档实例
**返回**: `array` - 可搜索数据数组
**示例**:
```php
$searchableData = $service->prepareSearchableData($document);
```
---
## MarkdownRenderService
Markdown 渲染服务,将 Markdown 转换为 HTML。
**位置**: `app/Services/MarkdownRenderService.php`
### 方法
#### render()
将 Markdown 渲染为 HTML。
```php
public function render(string $markdown): string
```
**参数**:
- `$markdown` - Markdown 内容
**返回**: `string` - 渲染后的 HTML
**示例**:
```php
use App\Services\MarkdownRenderService;
$service = app(MarkdownRenderService::class);
$html = $service->render($markdown);
```
#### sanitize()
清理 HTML 内容,防止 XSS 攻击。
```php
public function sanitize(string $html): string
```
**参数**:
- `$html` - HTML 内容
**返回**: `string` - 清理后的 HTML
**示例**:
```php
$safeHtml = $service->sanitize($html);
```
#### extractPreview()
从 Markdown 中提取摘要。
```php
public function extractPreview(string $markdown, int $length = 200): string
```
**参数**:
- `$markdown` - Markdown 内容
- `$length` - 摘要长度(默认 200 字符)
**返回**: `string` - 摘要文本
**示例**:
```php
$preview = $service->extractPreview($markdown, 150);
```
---
## SecurityLogger
安全日志记录服务。
**位置**: `app/Services/SecurityLogger.php`
### 方法
#### logUnauthorizedAccess()
记录未授权访问尝试。
```php
public function logUnauthorizedAccess(
User $user,
Document $document,
string $action
): void
```
**参数**:
- `$user` - 用户实例
- `$document` - 文档实例
- `$action` - 尝试的操作(如 'view', 'download'
**示例**:
```php
use App\Services\SecurityLogger;
$logger = app(SecurityLogger::class);
$logger->logUnauthorizedAccess(
user: auth()->user(),
document: $document,
action: 'download'
);
```
#### logDocumentAccess()
记录文档访问。
```php
public function logDocumentAccess(
User $user,
Document $document,
string $action
): void
```
**参数**:
- `$user` - 用户实例
- `$document` - 文档实例
- `$action` - 操作类型
**示例**:
```php
$logger->logDocumentAccess(
user: auth()->user(),
document: $document,
action: 'view'
);
```
---
## 模型查询作用域
### Document 模型
#### accessibleBy()
获取用户可访问的文档。
```php
Document::accessibleBy($user)->get();
```
**参数**:
- `$user` - 用户实例
**返回**: 查询构建器
**示例**:
```php
// 获取用户可访问的所有文档
$documents = Document::accessibleBy(auth()->user())->get();
// 结合其他查询条件
$recentDocuments = Document::accessibleBy(auth()->user())
->where('created_at', '>=', now()->subDays(7))
->orderBy('created_at', 'desc')
->get();
```
#### global()
获取全局文档。
```php
Document::global()->get();
```
**返回**: 查询构建器
**示例**:
```php
$globalDocuments = Document::global()->get();
```
#### dedicated()
获取专用文档。
```php
Document::dedicated()->get();
```
**返回**: 查询构建器
**示例**:
```php
$dedicatedDocuments = Document::dedicated()
->where('group_id', $groupId)
->get();
```
---
## 策略方法
### DocumentPolicy
#### view()
检查用户是否可以查看文档。
```php
Gate::allows('view', $document);
```
**参数**:
- `$document` - 文档实例
**返回**: `bool`
**示例**:
```php
if (Gate::allows('view', $document)) {
// 用户可以查看文档
}
// 或使用 authorize 方法
$this->authorize('view', $document);
```
#### download()
检查用户是否可以下载文档。
```php
Gate::allows('download', $document);
```
**参数**:
- `$document` - 文档实例
**返回**: `bool`
**示例**:
```php
if (Gate::denies('download', $document)) {
abort(403, '您没有权限下载此文档');
}
```
---
## 队列任务
### ConvertDocumentToMarkdown
文档转换队列任务。
**位置**: `app/Jobs/ConvertDocumentToMarkdown.php`
**分发任务**:
```php
use App\Jobs\ConvertDocumentToMarkdown;
ConvertDocumentToMarkdown::dispatch($document);
```
**延迟分发**:
```php
ConvertDocumentToMarkdown::dispatch($document)
->delay(now()->addMinutes(5));
```
**指定队列**:
```php
ConvertDocumentToMarkdown::dispatch($document)
->onQueue('documents');
```
---
## 事件和监听器
### 文档事件
系统会在文档生命周期的关键点触发事件:
#### 文档创建后
```php
// 自动触发转换和索引
Document::created(function ($document) {
// 队列转换任务
// 索引到 Meilisearch
});
```
#### 文档更新后
```php
Document::updated(function ($document) {
// 如果文件变更,重新转换
// 更新 Meilisearch 索引
});
```
#### 文档删除后
```php
Document::deleted(function ($document) {
// 删除文件
// 从 Meilisearch 移除索引
});
```
---
## 配置选项
### 文档转换配置
**文件**: `config/documents.php`
```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`
```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
文件格式验证失败或必填字段缺失。
```php
try {
$document = $service->uploadDocument(...);
} catch (ValidationException $e) {
return back()->withErrors($e->errors());
}
```
#### AuthorizationException
用户无权执行操作。
```php
try {
$this->authorize('download', $document);
} catch (AuthorizationException $e) {
abort(403, '您没有权限下载此文档');
}
```
#### FileNotFoundException
文件不存在。
```php
try {
return $service->downloadDocument($document, $user);
} catch (FileNotFoundException $e) {
abort(404, '文件不存在');
}
```
#### ConversionException
文档转换失败。
```php
try {
$markdown = $service->convertToMarkdown($document);
} catch (ConversionException $e) {
Log::error('文档转换失败', [
'document_id' => $document->id,
'error' => $e->getMessage(),
]);
}
```
---
## 最佳实践
### 1. 使用依赖注入
```php
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. 使用事务处理
```php
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. 使用查询作用域
```php
// 好的做法
$documents = Document::accessibleBy(auth()->user())
->where('type', 'global')
->latest()
->paginate(20);
// 避免手动实现权限过滤
```
### 4. 异步处理耗时操作
```php
// 文档转换应该异步处理
ConvertDocumentToMarkdown::dispatch($document);
// 而不是同步执行
// $service->convertToMarkdown($document); // 避免
```
---
**最后更新**2025-12-05