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:
852
docs/API_REFERENCE.md
Normal file
852
docs/API_REFERENCE.md
Normal 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
|
||||
Reference in New Issue
Block a user