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
|
||||
816
docs/DEPLOYMENT.md
Normal file
816
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,816 @@
|
||||
# 知识库系统部署指南
|
||||
|
||||
本文档提供知识库系统的详细部署步骤和配置说明。
|
||||
|
||||
## 目录
|
||||
|
||||
1. [服务器要求](#服务器要求)
|
||||
2. [安装依赖](#安装依赖)
|
||||
3. [项目部署](#项目部署)
|
||||
4. [配置服务](#配置服务)
|
||||
5. [性能优化](#性能优化)
|
||||
6. [监控和维护](#监控和维护)
|
||||
7. [故障排除](#故障排除)
|
||||
|
||||
## 服务器要求
|
||||
|
||||
### 最低配置
|
||||
- **CPU**: 2 核心
|
||||
- **内存**: 4GB RAM
|
||||
- **存储**: 50GB SSD
|
||||
- **操作系统**: Ubuntu 20.04+ / CentOS 8+ / Debian 11+
|
||||
|
||||
### 推荐配置
|
||||
- **CPU**: 4 核心
|
||||
- **内存**: 8GB RAM
|
||||
- **存储**: 100GB SSD
|
||||
- **操作系统**: Ubuntu 22.04 LTS
|
||||
|
||||
### 软件要求
|
||||
- PHP 8.1 或更高版本
|
||||
- MySQL 8.0+ 或 PostgreSQL 13+
|
||||
- Redis 6.0+
|
||||
- Nginx 1.18+ 或 Apache 2.4+
|
||||
- Composer 2.x
|
||||
- Node.js 18+ 和 npm
|
||||
- Meilisearch 1.5+
|
||||
- Pandoc 2.x+(用于文档转换)
|
||||
- Supervisor(用于管理队列进程)
|
||||
|
||||
## 安装依赖
|
||||
|
||||
### 1. 安装 PHP 和扩展
|
||||
|
||||
#### Ubuntu/Debian
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y php8.1 php8.1-fpm php8.1-cli php8.1-common \
|
||||
php8.1-mysql php8.1-pgsql php8.1-redis php8.1-xml php8.1-mbstring \
|
||||
php8.1-curl php8.1-zip php8.1-gd php8.1-intl php8.1-bcmath
|
||||
```
|
||||
|
||||
#### CentOS/RHEL
|
||||
```bash
|
||||
sudo dnf install -y php php-fpm php-cli php-common php-mysqlnd \
|
||||
php-pgsql php-redis php-xml php-mbstring php-curl php-zip \
|
||||
php-gd php-intl php-bcmath
|
||||
```
|
||||
|
||||
### 2. 安装 Composer
|
||||
|
||||
```bash
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
sudo mv composer.phar /usr/local/bin/composer
|
||||
sudo chmod +x /usr/local/bin/composer
|
||||
```
|
||||
|
||||
### 3. 安装 Node.js 和 npm
|
||||
|
||||
```bash
|
||||
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
```
|
||||
|
||||
### 4. 安装 MySQL
|
||||
|
||||
```bash
|
||||
sudo apt install -y mysql-server
|
||||
sudo mysql_secure_installation
|
||||
```
|
||||
|
||||
创建数据库和用户:
|
||||
|
||||
```sql
|
||||
CREATE DATABASE knowledge_base CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER 'kb_user'@'localhost' IDENTIFIED BY 'your_secure_password';
|
||||
GRANT ALL PRIVILEGES ON knowledge_base.* TO 'kb_user'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
### 5. 安装 Redis
|
||||
|
||||
```bash
|
||||
sudo apt install -y redis-server
|
||||
sudo systemctl enable redis-server
|
||||
sudo systemctl start redis-server
|
||||
```
|
||||
|
||||
### 6. 安装 Meilisearch
|
||||
|
||||
#### 使用 Docker(推荐)
|
||||
```bash
|
||||
docker run -d \
|
||||
--name meilisearch \
|
||||
-p 7700:7700 \
|
||||
-v $(pwd)/storage/meilisearch:/meili_data \
|
||||
-e MEILI_MASTER_KEY='your_master_key_here' \
|
||||
getmeili/meilisearch:v1.5
|
||||
```
|
||||
|
||||
#### 直接安装
|
||||
```bash
|
||||
curl -L https://install.meilisearch.com | sh
|
||||
sudo mv ./meilisearch /usr/local/bin/
|
||||
```
|
||||
|
||||
创建 systemd 服务:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/meilisearch.service
|
||||
```
|
||||
|
||||
内容:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Meilisearch
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
ExecStart=/usr/local/bin/meilisearch --master-key="your_master_key_here" --db-path=/var/lib/meilisearch/data
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
启动服务:
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable meilisearch
|
||||
sudo systemctl start meilisearch
|
||||
```
|
||||
|
||||
### 7. 安装 Pandoc
|
||||
|
||||
```bash
|
||||
sudo apt install -y pandoc
|
||||
```
|
||||
|
||||
或下载最新版本:
|
||||
|
||||
```bash
|
||||
wget https://github.com/jgm/pandoc/releases/download/3.1.11/pandoc-3.1.11-1-amd64.deb
|
||||
sudo dpkg -i pandoc-3.1.11-1-amd64.deb
|
||||
```
|
||||
|
||||
### 8. 安装 Supervisor
|
||||
|
||||
```bash
|
||||
sudo apt install -y supervisor
|
||||
sudo systemctl enable supervisor
|
||||
sudo systemctl start supervisor
|
||||
```
|
||||
|
||||
## 项目部署
|
||||
|
||||
### 1. 克隆项目
|
||||
|
||||
```bash
|
||||
cd /var/www
|
||||
sudo git clone <repository-url> knowledge-base
|
||||
cd knowledge-base
|
||||
```
|
||||
|
||||
### 2. 设置权限
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /var/www/knowledge-base
|
||||
sudo chmod -R 755 /var/www/knowledge-base
|
||||
sudo chmod -R 775 /var/www/knowledge-base/storage
|
||||
sudo chmod -R 775 /var/www/knowledge-base/bootstrap/cache
|
||||
```
|
||||
|
||||
### 3. 安装依赖
|
||||
|
||||
```bash
|
||||
# PHP 依赖
|
||||
composer install --no-dev --optimize-autoloader
|
||||
|
||||
# 前端依赖
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 4. 配置环境变量
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
配置以下关键参数:
|
||||
|
||||
```env
|
||||
APP_NAME="知识库系统"
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_URL=https://your-domain.com
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=knowledge_base
|
||||
DB_USERNAME=kb_user
|
||||
DB_PASSWORD=your_secure_password
|
||||
|
||||
CACHE_DRIVER=redis
|
||||
SESSION_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MEILISEARCH_HOST=http://127.0.0.1:7700
|
||||
MEILISEARCH_KEY=your_master_key_here
|
||||
SCOUT_DRIVER=meilisearch
|
||||
|
||||
DOCUMENT_CONVERSION_DRIVER=pandoc
|
||||
PANDOC_PATH=/usr/bin/pandoc
|
||||
CONVERSION_TIMEOUT=300
|
||||
|
||||
FILESYSTEM_DISK=local
|
||||
```
|
||||
|
||||
### 5. 生成应用密钥
|
||||
|
||||
```bash
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
### 6. 运行数据库迁移
|
||||
|
||||
```bash
|
||||
php artisan migrate --force
|
||||
```
|
||||
|
||||
### 7. 创建存储目录
|
||||
|
||||
```bash
|
||||
mkdir -p storage/app/private/documents
|
||||
mkdir -p storage/app/private/markdown
|
||||
sudo chown -R www-data:www-data storage/app/private
|
||||
sudo chmod -R 775 storage/app/private
|
||||
```
|
||||
|
||||
### 8. 优化应用
|
||||
|
||||
```bash
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
php artisan filament:optimize
|
||||
```
|
||||
|
||||
### 9. 创建管理员用户
|
||||
|
||||
```bash
|
||||
php artisan make:filament-user
|
||||
```
|
||||
|
||||
## 配置服务
|
||||
|
||||
### 1. 配置 Nginx
|
||||
|
||||
创建站点配置:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/nginx/sites-available/knowledge-base
|
||||
```
|
||||
|
||||
内容:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name your-domain.com;
|
||||
root /var/www/knowledge-base/public;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
error_page 404 /index.php;
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# 文件上传大小限制
|
||||
client_max_body_size 50M;
|
||||
}
|
||||
```
|
||||
|
||||
启用站点:
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/knowledge-base /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
### 2. 配置 SSL(使用 Let's Encrypt)
|
||||
|
||||
```bash
|
||||
sudo apt install -y certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
### 3. 配置队列工作进程
|
||||
|
||||
创建 Supervisor 配置:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/supervisor/conf.d/knowledge-base-worker.conf
|
||||
```
|
||||
|
||||
内容:
|
||||
|
||||
```ini
|
||||
[program:knowledge-base-worker]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /var/www/knowledge-base/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopasgroup=true
|
||||
killasgroup=true
|
||||
user=www-data
|
||||
numprocs=2
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/www/knowledge-base/storage/logs/worker.log
|
||||
stopwaitsecs=3600
|
||||
```
|
||||
|
||||
重新加载 Supervisor:
|
||||
|
||||
```bash
|
||||
sudo supervisorctl reread
|
||||
sudo supervisorctl update
|
||||
sudo supervisorctl start knowledge-base-worker:*
|
||||
```
|
||||
|
||||
### 4. 配置定时任务
|
||||
|
||||
编辑 crontab:
|
||||
|
||||
```bash
|
||||
sudo crontab -e -u www-data
|
||||
```
|
||||
|
||||
添加:
|
||||
|
||||
```cron
|
||||
* * * * * cd /var/www/knowledge-base && php artisan schedule:run >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. PHP-FPM 优化
|
||||
|
||||
编辑 PHP-FPM 配置:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/php/8.1/fpm/pool.d/www.conf
|
||||
```
|
||||
|
||||
调整参数:
|
||||
|
||||
```ini
|
||||
pm = dynamic
|
||||
pm.max_children = 50
|
||||
pm.start_servers = 10
|
||||
pm.min_spare_servers = 5
|
||||
pm.max_spare_servers = 20
|
||||
pm.max_requests = 500
|
||||
```
|
||||
|
||||
重启 PHP-FPM:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart php8.1-fpm
|
||||
```
|
||||
|
||||
### 2. Redis 优化
|
||||
|
||||
编辑 Redis 配置:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/redis/redis.conf
|
||||
```
|
||||
|
||||
调整参数:
|
||||
|
||||
```conf
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
重启 Redis:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart redis-server
|
||||
```
|
||||
|
||||
### 3. MySQL 优化
|
||||
|
||||
编辑 MySQL 配置:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
|
||||
```
|
||||
|
||||
添加优化参数:
|
||||
|
||||
```ini
|
||||
[mysqld]
|
||||
innodb_buffer_pool_size = 2G
|
||||
innodb_log_file_size = 256M
|
||||
innodb_flush_log_at_trx_commit = 2
|
||||
innodb_flush_method = O_DIRECT
|
||||
query_cache_size = 0
|
||||
query_cache_type = 0
|
||||
```
|
||||
|
||||
重启 MySQL:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart mysql
|
||||
```
|
||||
|
||||
### 4. Opcache 配置
|
||||
|
||||
编辑 PHP 配置:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/php/8.1/fpm/conf.d/10-opcache.ini
|
||||
```
|
||||
|
||||
内容:
|
||||
|
||||
```ini
|
||||
opcache.enable=1
|
||||
opcache.memory_consumption=256
|
||||
opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=10000
|
||||
opcache.revalidate_freq=60
|
||||
opcache.fast_shutdown=1
|
||||
```
|
||||
|
||||
## 监控和维护
|
||||
|
||||
### 1. 日志监控
|
||||
|
||||
查看应用日志:
|
||||
|
||||
```bash
|
||||
tail -f /var/www/knowledge-base/storage/logs/laravel.log
|
||||
```
|
||||
|
||||
查看队列工作进程日志:
|
||||
|
||||
```bash
|
||||
tail -f /var/www/knowledge-base/storage/logs/worker.log
|
||||
```
|
||||
|
||||
查看 Nginx 日志:
|
||||
|
||||
```bash
|
||||
tail -f /var/log/nginx/access.log
|
||||
tail -f /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
### 2. 定期备份
|
||||
|
||||
#### 数据库备份
|
||||
|
||||
创建备份脚本:
|
||||
|
||||
```bash
|
||||
sudo nano /usr/local/bin/backup-kb-db.sh
|
||||
```
|
||||
|
||||
内容:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/var/backups/knowledge-base"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
mysqldump -u kb_user -p'your_secure_password' knowledge_base | gzip > $BACKUP_DIR/db_$DATE.sql.gz
|
||||
|
||||
# 保留最近 7 天的备份
|
||||
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +7 -delete
|
||||
```
|
||||
|
||||
设置权限并添加到 crontab:
|
||||
|
||||
```bash
|
||||
sudo chmod +x /usr/local/bin/backup-kb-db.sh
|
||||
sudo crontab -e
|
||||
```
|
||||
|
||||
添加每日备份任务:
|
||||
|
||||
```cron
|
||||
0 2 * * * /usr/local/bin/backup-kb-db.sh
|
||||
```
|
||||
|
||||
#### 文件备份
|
||||
|
||||
```bash
|
||||
sudo nano /usr/local/bin/backup-kb-files.sh
|
||||
```
|
||||
|
||||
内容:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/var/backups/knowledge-base"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
tar -czf $BACKUP_DIR/files_$DATE.tar.gz \
|
||||
/var/www/knowledge-base/storage/app/private/documents \
|
||||
/var/www/knowledge-base/storage/app/private/markdown
|
||||
|
||||
# 保留最近 7 天的备份
|
||||
find $BACKUP_DIR -name "files_*.tar.gz" -mtime +7 -delete
|
||||
```
|
||||
|
||||
### 3. 系统监控
|
||||
|
||||
安装监控工具:
|
||||
|
||||
```bash
|
||||
sudo apt install -y htop iotop nethogs
|
||||
```
|
||||
|
||||
监控系统资源:
|
||||
|
||||
```bash
|
||||
# CPU 和内存
|
||||
htop
|
||||
|
||||
# 磁盘 I/O
|
||||
iotop
|
||||
|
||||
# 网络流量
|
||||
nethogs
|
||||
```
|
||||
|
||||
### 4. 应用健康检查
|
||||
|
||||
创建健康检查脚本:
|
||||
|
||||
```bash
|
||||
sudo nano /usr/local/bin/check-kb-health.sh
|
||||
```
|
||||
|
||||
内容:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# 检查 Web 服务
|
||||
if ! curl -f http://localhost > /dev/null 2>&1; then
|
||||
echo "Web service is down!"
|
||||
sudo systemctl restart nginx
|
||||
fi
|
||||
|
||||
# 检查队列工作进程
|
||||
if ! sudo supervisorctl status knowledge-base-worker:* | grep RUNNING > /dev/null; then
|
||||
echo "Queue worker is down!"
|
||||
sudo supervisorctl restart knowledge-base-worker:*
|
||||
fi
|
||||
|
||||
# 检查 Meilisearch
|
||||
if ! curl -f http://localhost:7700/health > /dev/null 2>&1; then
|
||||
echo "Meilisearch is down!"
|
||||
sudo systemctl restart meilisearch
|
||||
fi
|
||||
```
|
||||
|
||||
添加到 crontab(每 5 分钟检查一次):
|
||||
|
||||
```cron
|
||||
*/5 * * * * /usr/local/bin/check-kb-health.sh
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 文件上传失败
|
||||
|
||||
检查 PHP 配置:
|
||||
|
||||
```bash
|
||||
php -i | grep upload_max_filesize
|
||||
php -i | grep post_max_size
|
||||
```
|
||||
|
||||
如需调整,编辑 PHP 配置:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/php/8.1/fpm/php.ini
|
||||
```
|
||||
|
||||
修改:
|
||||
|
||||
```ini
|
||||
upload_max_filesize = 50M
|
||||
post_max_size = 50M
|
||||
```
|
||||
|
||||
重启 PHP-FPM:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart php8.1-fpm
|
||||
```
|
||||
|
||||
### 2. 队列任务不执行
|
||||
|
||||
检查队列工作进程状态:
|
||||
|
||||
```bash
|
||||
sudo supervisorctl status knowledge-base-worker:*
|
||||
```
|
||||
|
||||
重启工作进程:
|
||||
|
||||
```bash
|
||||
sudo supervisorctl restart knowledge-base-worker:*
|
||||
```
|
||||
|
||||
查看队列日志:
|
||||
|
||||
```bash
|
||||
tail -f /var/www/knowledge-base/storage/logs/worker.log
|
||||
```
|
||||
|
||||
### 3. Meilisearch 连接失败
|
||||
|
||||
检查 Meilisearch 状态:
|
||||
|
||||
```bash
|
||||
curl http://localhost:7700/health
|
||||
```
|
||||
|
||||
检查 Meilisearch 日志:
|
||||
|
||||
```bash
|
||||
sudo journalctl -u meilisearch -f
|
||||
```
|
||||
|
||||
重启 Meilisearch:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart meilisearch
|
||||
```
|
||||
|
||||
### 4. 文档转换失败
|
||||
|
||||
检查 Pandoc 是否安装:
|
||||
|
||||
```bash
|
||||
which pandoc
|
||||
pandoc --version
|
||||
```
|
||||
|
||||
检查转换日志:
|
||||
|
||||
```bash
|
||||
grep "conversion" /var/www/knowledge-base/storage/logs/laravel.log
|
||||
```
|
||||
|
||||
手动测试转换:
|
||||
|
||||
```bash
|
||||
pandoc test.docx -o test.md
|
||||
```
|
||||
|
||||
### 5. 权限问题
|
||||
|
||||
重置存储目录权限:
|
||||
|
||||
```bash
|
||||
cd /var/www/knowledge-base
|
||||
sudo chown -R www-data:www-data storage bootstrap/cache
|
||||
sudo chmod -R 775 storage bootstrap/cache
|
||||
```
|
||||
|
||||
### 6. 缓存问题
|
||||
|
||||
清除所有缓存:
|
||||
|
||||
```bash
|
||||
cd /var/www/knowledge-base
|
||||
php artisan cache:clear
|
||||
php artisan config:clear
|
||||
php artisan route:clear
|
||||
php artisan view:clear
|
||||
```
|
||||
|
||||
重新生成缓存:
|
||||
|
||||
```bash
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
```
|
||||
|
||||
## 更新部署
|
||||
|
||||
### 1. 拉取最新代码
|
||||
|
||||
```bash
|
||||
cd /var/www/knowledge-base
|
||||
sudo -u www-data git pull origin main
|
||||
```
|
||||
|
||||
### 2. 更新依赖
|
||||
|
||||
```bash
|
||||
sudo -u www-data composer install --no-dev --optimize-autoloader
|
||||
sudo -u www-data npm install
|
||||
sudo -u www-data npm run build
|
||||
```
|
||||
|
||||
### 3. 运行迁移
|
||||
|
||||
```bash
|
||||
php artisan migrate --force
|
||||
```
|
||||
|
||||
### 4. 清除和重建缓存
|
||||
|
||||
```bash
|
||||
php artisan cache:clear
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
php artisan filament:optimize
|
||||
```
|
||||
|
||||
### 5. 重启服务
|
||||
|
||||
```bash
|
||||
sudo systemctl reload php8.1-fpm
|
||||
sudo systemctl reload nginx
|
||||
sudo supervisorctl restart knowledge-base-worker:*
|
||||
```
|
||||
|
||||
## 安全建议
|
||||
|
||||
1. **定期更新系统和软件包**
|
||||
```bash
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
```
|
||||
|
||||
2. **配置防火墙**
|
||||
```bash
|
||||
sudo ufw allow 22/tcp
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
3. **禁用不必要的 PHP 函数**
|
||||
编辑 `php.ini`,添加:
|
||||
```ini
|
||||
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
|
||||
```
|
||||
|
||||
4. **设置强密码策略**
|
||||
5. **定期审查安全日志**
|
||||
6. **启用 HTTPS**
|
||||
7. **限制文件上传类型**
|
||||
8. **定期备份数据**
|
||||
|
||||
## 联系支持
|
||||
|
||||
如遇到部署问题,请联系技术支持团队。
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2025-12-05
|
||||
171
docs/DOCUMENT_CONVERSION_GUIDE.md
Normal file
171
docs/DOCUMENT_CONVERSION_GUIDE.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# 文档转换服务使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
DocumentConversionService 负责将上传的 Word 文档(.doc/.docx)转换为 Markdown 格式。转换过程是异步的,通过队列系统处理。
|
||||
|
||||
## 功能特性
|
||||
|
||||
1. **自动转换**: 文档上传后自动触发转换
|
||||
2. **异步处理**: 使用队列系统,不阻塞用户操作
|
||||
3. **错误处理**: 转换失败时记录错误,但不影响文档的基本功能
|
||||
4. **重试机制**: 转换失败后自动重试(默认 3 次)
|
||||
5. **预览生成**: 自动生成 Markdown 内容的预览(前 500 字符)
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 队列转换(推荐)
|
||||
|
||||
```php
|
||||
use App\Services\DocumentConversionService;
|
||||
use App\Models\Document;
|
||||
|
||||
$document = Document::find($id);
|
||||
$conversionService = app(DocumentConversionService::class);
|
||||
|
||||
// 将转换任务加入队列
|
||||
$conversionService->queueConversion($document);
|
||||
```
|
||||
|
||||
### 2. 同步转换(不推荐用于生产环境)
|
||||
|
||||
```php
|
||||
use App\Services\DocumentConversionService;
|
||||
use App\Models\Document;
|
||||
|
||||
$document = Document::find($id);
|
||||
$conversionService = app(DocumentConversionService::class);
|
||||
|
||||
try {
|
||||
// 转换文档
|
||||
$markdown = $conversionService->convertToMarkdown($document);
|
||||
|
||||
// 保存 Markdown 文件
|
||||
$markdownPath = $conversionService->saveMarkdownToFile($document, $markdown);
|
||||
|
||||
// 更新文档信息
|
||||
$conversionService->updateDocumentMarkdown($document, $markdownPath);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 处理转换失败
|
||||
$conversionService->handleConversionFailure($document, $e);
|
||||
}
|
||||
```
|
||||
|
||||
## 转换状态
|
||||
|
||||
文档的 `conversion_status` 字段有以下几种状态:
|
||||
|
||||
- `pending`: 等待转换
|
||||
- `processing`: 正在转换
|
||||
- `completed`: 转换完成
|
||||
- `failed`: 转换失败
|
||||
|
||||
## 配置
|
||||
|
||||
转换相关的配置在 `config/documents.php` 文件中:
|
||||
|
||||
```php
|
||||
'conversion' => [
|
||||
'driver' => 'pandoc', // 转换驱动
|
||||
'pandoc_path' => '/usr/local/bin/pandoc', // Pandoc 路径
|
||||
'timeout' => 300, // 超时时间(秒)
|
||||
'queue' => 'documents', // 队列名称
|
||||
'retry_times' => 3, // 重试次数
|
||||
'retry_delay' => 60, // 重试延迟(秒)
|
||||
],
|
||||
```
|
||||
|
||||
## 队列工作进程
|
||||
|
||||
确保队列工作进程正在运行:
|
||||
|
||||
```bash
|
||||
# 启动队列工作进程
|
||||
php artisan queue:work --queue=documents
|
||||
|
||||
# 或者使用 Supervisor 管理队列进程
|
||||
```
|
||||
|
||||
## 依赖要求
|
||||
|
||||
### Pandoc
|
||||
|
||||
系统需要安装 Pandoc 才能进行文档转换:
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
brew install pandoc
|
||||
```
|
||||
|
||||
**Ubuntu/Debian:**
|
||||
```bash
|
||||
sudo apt-get install pandoc
|
||||
```
|
||||
|
||||
**验证安装:**
|
||||
```bash
|
||||
pandoc --version
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
转换失败时,系统会:
|
||||
|
||||
1. 记录详细的错误日志
|
||||
2. 更新文档的 `conversion_status` 为 `failed`
|
||||
3. 在 `conversion_error` 字段中保存错误信息
|
||||
4. 保留原始 Word 文档,用户仍可下载
|
||||
|
||||
## 监控和调试
|
||||
|
||||
### 查看转换日志
|
||||
|
||||
```bash
|
||||
tail -f storage/logs/laravel.log | grep "文档转换"
|
||||
```
|
||||
|
||||
### 查看队列任务
|
||||
|
||||
```bash
|
||||
php artisan queue:failed
|
||||
```
|
||||
|
||||
### 重试失败的任务
|
||||
|
||||
```bash
|
||||
# 重试所有失败的任务
|
||||
php artisan queue:retry all
|
||||
|
||||
# 重试特定任务
|
||||
php artisan queue:retry {job-id}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **使用 Redis 队列**: 比数据库队列更快
|
||||
2. **增加队列工作进程**: 并行处理多个转换任务
|
||||
3. **调整超时时间**: 根据文档大小调整 `timeout` 配置
|
||||
4. **监控队列长度**: 避免队列积压
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 转换一直处于 processing 状态
|
||||
|
||||
- 检查队列工作进程是否运行
|
||||
- 检查 Pandoc 是否正确安装
|
||||
- 查看错误日志
|
||||
|
||||
### 转换失败
|
||||
|
||||
- 检查 Pandoc 路径配置是否正确
|
||||
- 检查文档文件是否存在
|
||||
- 检查文档文件是否损坏
|
||||
- 查看 `conversion_error` 字段的错误信息
|
||||
|
||||
### 队列任务不执行
|
||||
|
||||
- 确认队列连接配置正确(Redis/Database)
|
||||
- 确认队列工作进程正在运行
|
||||
- 检查队列名称是否匹配
|
||||
|
||||
146
docs/DOCUMENT_PREVIEW_GUIDE.md
Normal file
146
docs/DOCUMENT_PREVIEW_GUIDE.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# 文档预览功能使用指南
|
||||
|
||||
## 功能概述
|
||||
|
||||
知识库系统现在支持在线预览 Word 文档(.doc 和 .docx 格式)。用户可以在文档查看页面直接查看文档内容,无需下载。
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心组件
|
||||
|
||||
1. **DocumentPreviewService** (`app/Services/DocumentPreviewService.php`)
|
||||
- 负责将 Word 文档转换为 HTML 格式
|
||||
- 使用 PHPWord 库进行文档解析
|
||||
- 提供文档预览能力检查
|
||||
|
||||
2. **ViewDocument 页面** (`app/Filament/Resources/DocumentResource/Pages/ViewDocument.php`)
|
||||
- 使用 Filament Infolist 显示文档信息
|
||||
- 集成文档预览组件
|
||||
- 提供下载按钮
|
||||
|
||||
3. **预览视图** (`resources/views/filament/resources/document/preview.blade.php`)
|
||||
- 渲染文档预览 HTML
|
||||
- 处理错误和不支持的格式
|
||||
- 提供友好的用户界面
|
||||
|
||||
### 依赖库
|
||||
|
||||
- **phpoffice/phpword**: 用于读取和转换 Word 文档
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 文档信息展示
|
||||
|
||||
在查看页面顶部显示:
|
||||
- 文档标题
|
||||
- 文档描述
|
||||
- 文档类型(全局/专用知识库)
|
||||
- 所属分组(专用文档)
|
||||
- 上传者
|
||||
- 文件名
|
||||
- 文件大小
|
||||
- 上传时间
|
||||
- 更新时间
|
||||
|
||||
### 2. 文档预览
|
||||
|
||||
- **支持格式**: .doc, .docx
|
||||
- **预览内容**:
|
||||
- 文本内容
|
||||
- 基本格式(标题、段落、列表等)
|
||||
- 表格
|
||||
- 图片(如果有)
|
||||
- **最大高度**: 600px,超出部分可滚动查看
|
||||
|
||||
### 3. 错误处理
|
||||
|
||||
系统会优雅地处理以下情况:
|
||||
- 文档文件不存在
|
||||
- 文档格式不支持预览
|
||||
- 文档转换失败
|
||||
|
||||
### 4. 下载功能
|
||||
|
||||
- 在页面顶部提供"下载文档"按钮
|
||||
- 自动记录下载日志
|
||||
- 验证用户权限
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 查看文档
|
||||
|
||||
1. 登录系统
|
||||
2. 进入"文档管理"
|
||||
3. 点击任意文档的"查看"按钮
|
||||
4. 系统会自动显示文档信息和预览
|
||||
|
||||
### 下载文档
|
||||
|
||||
1. 在文档查看页面
|
||||
2. 点击顶部的"下载文档"按钮
|
||||
3. 文档会自动下载到本地
|
||||
|
||||
## API 方法
|
||||
|
||||
### DocumentPreviewService
|
||||
|
||||
```php
|
||||
// 检查文档是否可以预览
|
||||
$canPreview = $previewService->canPreview($document);
|
||||
|
||||
// 将文档转换为 HTML
|
||||
$htmlContent = $previewService->convertToHtml($document);
|
||||
|
||||
// 提取文档纯文本(用于搜索等)
|
||||
$text = $previewService->extractText($document);
|
||||
```
|
||||
|
||||
## 性能考虑
|
||||
|
||||
1. **首次加载**: 文档转换可能需要几秒钟,取决于文档大小和复杂度
|
||||
2. **缓存**: 目前未实现缓存,每次查看都会重新转换
|
||||
3. **大文件**: 建议对大文件(>10MB)提示用户下载查看
|
||||
|
||||
## 未来改进
|
||||
|
||||
1. **缓存机制**: 缓存转换后的 HTML,提高加载速度
|
||||
2. **更多格式**: 支持 PDF、Excel 等格式
|
||||
3. **全文搜索**: 利用提取的文本内容实现全文搜索
|
||||
4. **在线编辑**: 支持在线编辑文档内容
|
||||
5. **版本控制**: 支持文档版本管理
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 预览失败
|
||||
|
||||
如果预览失败,可能的原因:
|
||||
1. 文档文件损坏
|
||||
2. 文档格式不标准
|
||||
3. PHPWord 库不支持某些特殊格式
|
||||
|
||||
解决方法:
|
||||
- 检查文档是否可以在本地打开
|
||||
- 尝试重新上传文档
|
||||
- 使用下载功能获取原始文档
|
||||
|
||||
### 格式显示异常
|
||||
|
||||
预览版本可能与原始格式略有差异,这是正常现象。如需查看完整格式,请下载文档。
|
||||
|
||||
## 安全考虑
|
||||
|
||||
1. **权限验证**: 只有有权限的用户才能查看文档
|
||||
2. **文件隔离**: 文档存储在私有目录,不能直接访问
|
||||
3. **XSS 防护**: HTML 内容经过清理,防止 XSS 攻击
|
||||
|
||||
## 测试
|
||||
|
||||
运行测试:
|
||||
```bash
|
||||
php artisan test --filter=DocumentPreviewServiceTest
|
||||
```
|
||||
|
||||
测试覆盖:
|
||||
- 文档格式检查
|
||||
- 文档不存在时的错误处理
|
||||
- HTML 转换功能
|
||||
192
docs/DOCUMENT_SEARCH_GUIDE.md
Normal file
192
docs/DOCUMENT_SEARCH_GUIDE.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# 文档搜索功能指南
|
||||
|
||||
## 概述
|
||||
|
||||
文档搜索功能使用 Laravel Scout 和 Meilisearch 提供强大的全文搜索能力。系统会自动将转换完成的文档索引到 Meilisearch,用户可以通过关键词快速检索文档内容。
|
||||
|
||||
## 核心组件
|
||||
|
||||
### DocumentSearchService
|
||||
|
||||
文档搜索服务类,提供以下功能:
|
||||
|
||||
1. **search()** - 执行全文搜索
|
||||
- 搜索文档标题、描述和 Markdown 内容
|
||||
- 支持额外的筛选条件(类型、分组、上传者)
|
||||
- 自动应用用户权限过滤
|
||||
|
||||
2. **filterByUserPermissions()** - 权限过滤
|
||||
- 确保用户只能看到有权限访问的文档
|
||||
- 全局文档对所有用户可见
|
||||
- 专用文档只对所属分组的用户可见
|
||||
|
||||
3. **prepareSearchableData()** - 准备索引数据
|
||||
- 包含完整的 Markdown 内容用于搜索
|
||||
- 包含文档元数据(标题、描述、类型、分组等)
|
||||
|
||||
4. **indexDocument()** - 索引文档
|
||||
- 将文档添加到 Meilisearch 索引
|
||||
- 只索引转换完成的文档
|
||||
|
||||
5. **updateDocumentIndex()** - 更新索引
|
||||
- 更新已索引文档的信息
|
||||
- 如果文档不应被索引,则移除索引
|
||||
|
||||
6. **removeDocumentFromIndex()** - 移除索引
|
||||
- 从 Meilisearch 中删除文档索引
|
||||
|
||||
### DocumentObserver
|
||||
|
||||
文档观察者,自动管理文档的搜索索引:
|
||||
|
||||
- **created** - 文档创建时不立即索引(等待转换完成)
|
||||
- **updated** - 文档更新时检查转换状态并更新索引
|
||||
- 当 conversion_status 变为 'completed' 时自动索引
|
||||
- 当其他重要字段更新时也更新索引
|
||||
- **deleted** - 文档删除时移除索引
|
||||
- **restored** - 文档恢复时重新索引
|
||||
- **forceDeleted** - 强制删除时移除索引
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 文档索引流程
|
||||
|
||||
1. 用户上传 Word 文档
|
||||
2. 文档保存到数据库,conversion_status 设置为 'pending'
|
||||
3. 转换任务加入队列
|
||||
4. 转换完成后,updateDocumentMarkdown() 将 conversion_status 更新为 'completed'
|
||||
5. DocumentObserver 监听到 updated 事件
|
||||
6. 自动调用 DocumentSearchService::indexDocument() 创建索引
|
||||
7. 文档可以被搜索
|
||||
|
||||
### 搜索流程
|
||||
|
||||
1. 用户输入搜索关键词
|
||||
2. 调用 DocumentSearchService::search()
|
||||
3. 使用 Scout 在 Meilisearch 中搜索
|
||||
4. 应用额外的筛选条件
|
||||
5. 使用 filterByUserPermissions() 过滤结果
|
||||
6. 返回用户有权限访问的文档列表
|
||||
|
||||
## 配置
|
||||
|
||||
### Meilisearch 配置
|
||||
|
||||
在 `config/scout.php` 中配置:
|
||||
|
||||
```php
|
||||
'meilisearch' => [
|
||||
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
|
||||
'key' => env('MEILISEARCH_KEY'),
|
||||
'index-settings' => [
|
||||
'documents' => [
|
||||
'filterableAttributes' => ['type', 'group_id', 'uploaded_by', 'conversion_status'],
|
||||
'sortableAttributes' => ['created_at', 'title', 'updated_at'],
|
||||
'searchableAttributes' => ['title', 'description', 'markdown_content'],
|
||||
],
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
### 环境变量
|
||||
|
||||
在 `.env` 文件中配置:
|
||||
|
||||
```env
|
||||
SCOUT_DRIVER=meilisearch
|
||||
MEILISEARCH_HOST=http://127.0.0.1:7700
|
||||
MEILISEARCH_KEY=your-master-key
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本搜索
|
||||
|
||||
```php
|
||||
use App\Services\DocumentSearchService;
|
||||
|
||||
$searchService = app(DocumentSearchService::class);
|
||||
$user = auth()->user();
|
||||
|
||||
// 搜索关键词
|
||||
$results = $searchService->search('Laravel', $user);
|
||||
|
||||
// 带筛选条件的搜索
|
||||
$results = $searchService->search('Laravel', $user, [
|
||||
'type' => 'global',
|
||||
'group_id' => 1,
|
||||
]);
|
||||
```
|
||||
|
||||
### 手动索引管理
|
||||
|
||||
```php
|
||||
use App\Services\DocumentSearchService;
|
||||
use App\Models\Document;
|
||||
|
||||
$searchService = app(DocumentSearchService::class);
|
||||
$document = Document::find(1);
|
||||
|
||||
// 索引文档
|
||||
$searchService->indexDocument($document);
|
||||
|
||||
// 更新索引
|
||||
$searchService->updateDocumentIndex($document);
|
||||
|
||||
// 移除索引
|
||||
$searchService->removeDocumentFromIndex($document);
|
||||
```
|
||||
|
||||
### 批量重建索引
|
||||
|
||||
```php
|
||||
// 重建所有文档的索引
|
||||
php artisan scout:import "App\Models\Document"
|
||||
|
||||
// 清空索引
|
||||
php artisan scout:flush "App\Models\Document"
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
所有索引操作都包含错误处理:
|
||||
|
||||
- 索引失败不会影响文档的正常保存和使用
|
||||
- 所有错误都会记录到日志中
|
||||
- 搜索失败时返回空集合
|
||||
|
||||
## 性能考虑
|
||||
|
||||
1. **异步索引** - 文档转换和索引都在队列中异步处理
|
||||
2. **权限过滤** - 在应用层过滤搜索结果,确保数据安全
|
||||
3. **缓存策略** - 可以缓存热门搜索结果(待实现)
|
||||
4. **分页** - 搜索结果应该分页显示(在前端实现)
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 文档无法被搜索
|
||||
|
||||
1. 检查 conversion_status 是否为 'completed'
|
||||
2. 检查 Meilisearch 服务是否运行
|
||||
3. 检查文档是否已索引:`php artisan scout:import "App\Models\Document"`
|
||||
4. 查看日志文件中的错误信息
|
||||
|
||||
### Meilisearch 连接失败
|
||||
|
||||
1. 确认 Meilisearch 服务正在运行
|
||||
2. 检查 .env 中的 MEILISEARCH_HOST 和 MEILISEARCH_KEY
|
||||
3. 测试连接:`curl http://localhost:7700/health`
|
||||
|
||||
### 搜索结果为空
|
||||
|
||||
1. 确认文档已完成转换
|
||||
2. 确认用户有权限访问文档
|
||||
3. 检查搜索关键词是否正确
|
||||
4. 查看 Meilisearch 日志
|
||||
|
||||
## 下一步
|
||||
|
||||
- 实现搜索页面 UI(任务 25)
|
||||
- 添加搜索结果高亮显示
|
||||
- 实现搜索建议和自动补全
|
||||
- 添加高级搜索语法支持
|
||||
261
docs/FILENAME_PRESERVATION.md
Normal file
261
docs/FILENAME_PRESERVATION.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# 文件名保留功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
知识库系统现在会完整保留用户上传文档时的原始文件名,包括中文、特殊字符等。下载文档时,文件名将与上传时保持一致。
|
||||
|
||||
## 实现细节
|
||||
|
||||
### 1. Filament 表单配置
|
||||
|
||||
在 `DocumentResource` 的文件上传字段中添加了 `preserveFilenames()` 方法:
|
||||
|
||||
```php
|
||||
Forms\Components\FileUpload::make('file')
|
||||
->label('文档文件')
|
||||
->required()
|
||||
->acceptedFileTypes([...])
|
||||
->preserveFilenames() // 保留原始文件名
|
||||
->disk('local')
|
||||
->directory('documents/' . date('Y/m/d'))
|
||||
// ...
|
||||
```
|
||||
|
||||
### 2. 文件上传处理
|
||||
|
||||
#### CreateDocument 页面
|
||||
|
||||
```php
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
if (isset($data['file'])) {
|
||||
$filePath = $data['file'];
|
||||
$originalFileName = basename($filePath); // 获取原始文件名
|
||||
|
||||
$data['file_path'] = $filePath;
|
||||
$data['file_name'] = $originalFileName; // 保存原始文件名
|
||||
// ...
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
```
|
||||
|
||||
#### EditDocument 页面
|
||||
|
||||
```php
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
if (isset($data['file']) && $data['file'] !== $this->record->file_path) {
|
||||
$filePath = $data['file'];
|
||||
$originalFileName = basename($filePath); // 获取原始文件名
|
||||
|
||||
$data['file_path'] = $filePath;
|
||||
$data['file_name'] = $originalFileName; // 保存原始文件名
|
||||
// ...
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. DocumentService 更新
|
||||
|
||||
#### 上传方法
|
||||
|
||||
```php
|
||||
public function uploadDocument(...): Document
|
||||
{
|
||||
return DB::transaction(function () use (...) {
|
||||
// 获取原始文件名
|
||||
$originalFileName = $file->getClientOriginalName();
|
||||
|
||||
// 使用 storeAs 保存文件,保留原始文件名
|
||||
$directory = 'documents/' . date('Y/m/d');
|
||||
$filePath = $file->storeAs($directory, $originalFileName, 'local');
|
||||
|
||||
// 保存到数据库
|
||||
$document = Document::create([
|
||||
'file_path' => $filePath,
|
||||
'file_name' => $originalFileName, // 原始文件名
|
||||
// ...
|
||||
]);
|
||||
|
||||
return $document;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 下载方法
|
||||
|
||||
```php
|
||||
public function downloadDocument(Document $document, User $user): StreamedResponse
|
||||
{
|
||||
// 使用原始文件名作为下载文件名
|
||||
return Storage::disk('local')->download(
|
||||
$document->file_path,
|
||||
$document->file_name // 原始文件名
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 支持的文件名格式
|
||||
|
||||
### ✅ 完全支持
|
||||
|
||||
1. **中文文件名**
|
||||
- 示例:`知识库管理系统需求文档.docx`
|
||||
- 上传后保留:✅
|
||||
- 下载时保留:✅
|
||||
|
||||
2. **英文文件名**
|
||||
- 示例:`Requirements Document.docx`
|
||||
- 上传后保留:✅
|
||||
- 下载时保留:✅
|
||||
|
||||
3. **数字文件名**
|
||||
- 示例:`2024-Report.docx`
|
||||
- 上传后保留:✅
|
||||
- 下载时保留:✅
|
||||
|
||||
4. **特殊字符文件名**
|
||||
- 示例:`文档(2024-01-01)_v1.0.docx`
|
||||
- 支持的特殊字符:`()[]_-`
|
||||
- 上传后保留:✅
|
||||
- 下载时保留:✅
|
||||
|
||||
5. **混合格式文件名**
|
||||
- 示例:`Project_项目文档_2024.docx`
|
||||
- 上传后保留:✅
|
||||
- 下载时保留:✅
|
||||
|
||||
## 文件存储结构
|
||||
|
||||
文件按日期组织存储:
|
||||
|
||||
```
|
||||
storage/app/
|
||||
└── documents/
|
||||
└── 2024/
|
||||
└── 12/
|
||||
└── 04/
|
||||
├── 知识库管理系统需求文档.docx
|
||||
├── Requirements Document.docx
|
||||
└── 文档(2024-01-01)_v1.0.docx
|
||||
```
|
||||
|
||||
## 数据库字段
|
||||
|
||||
### documents 表
|
||||
|
||||
- `file_path`: 存储相对路径(如:`documents/2024/12/04/知识库管理系统需求文档.docx`)
|
||||
- `file_name`: 存储原始文件名(如:`知识库管理系统需求文档.docx`)
|
||||
- `file_size`: 文件大小(字节)
|
||||
- `mime_type`: MIME 类型
|
||||
|
||||
## 浏览器兼容性
|
||||
|
||||
### 下载文件名编码
|
||||
|
||||
对于包含非 ASCII 字符(如中文)的文件名,系统会自动处理编码:
|
||||
|
||||
```
|
||||
Content-Disposition: attachment; filename=document.docx; filename*=utf-8''%E7%9F%A5%E8%AF%86%E5%BA%93.docx
|
||||
```
|
||||
|
||||
- `filename`: ASCII 兼容的后备文件名
|
||||
- `filename*`: UTF-8 编码的完整文件名(RFC 5987)
|
||||
|
||||
### 支持的浏览器
|
||||
|
||||
- ✅ Chrome 80+
|
||||
- ✅ Firefox 75+
|
||||
- ✅ Safari 13+
|
||||
- ✅ Edge 80+
|
||||
- ✅ 移动浏览器(iOS Safari, Chrome Mobile)
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
### 测试文件:`tests/Feature/DocumentFileNameTest.php`
|
||||
|
||||
1. **test_上传文档时保留原始文件名**
|
||||
- 验证上传后 `file_name` 字段正确保存
|
||||
|
||||
2. **test_下载文档时使用原始文件名**
|
||||
- 验证下载响应头包含原始文件名
|
||||
|
||||
3. **test_中文文件名正确处理**
|
||||
- 验证中文文件名的完整支持
|
||||
|
||||
4. **test_特殊字符文件名正确处理**
|
||||
- 验证特殊字符的正确处理
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
php artisan test --filter=DocumentFileNameTest
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 文件名冲突
|
||||
|
||||
如果同一天上传了同名文件,后上传的文件会覆盖先上传的文件。建议:
|
||||
|
||||
- 用户在上传前检查文件名
|
||||
- 或者在文件名中添加时间戳或版本号
|
||||
|
||||
### 2. 文件名长度限制
|
||||
|
||||
- 数据库字段 `file_name` 限制:255 字符
|
||||
- 文件系统限制:通常为 255 字节
|
||||
- 建议文件名不超过 100 个字符
|
||||
|
||||
### 3. 特殊字符限制
|
||||
|
||||
某些特殊字符可能在不同操作系统上有限制:
|
||||
|
||||
- Windows: 不支持 `< > : " / \ | ? *`
|
||||
- Linux/Mac: 不支持 `/`
|
||||
|
||||
系统会自动处理这些字符,但建议避免使用。
|
||||
|
||||
## 安全考虑
|
||||
|
||||
### 1. 路径遍历攻击防护
|
||||
|
||||
- 使用 `basename()` 提取文件名,防止路径遍历
|
||||
- 文件存储在受控目录中
|
||||
|
||||
### 2. 文件名注入防护
|
||||
|
||||
- Laravel Storage 自动处理文件名转义
|
||||
- 数据库使用参数化查询
|
||||
|
||||
### 3. XSS 防护
|
||||
|
||||
- 文件名在显示时会被 Blade 模板自动转义
|
||||
- 下载响应头使用标准编码
|
||||
|
||||
## 未来改进
|
||||
|
||||
1. **文件名冲突处理**
|
||||
- 自动添加序号:`文档.docx` → `文档(1).docx`
|
||||
- 或添加时间戳:`文档.docx` → `文档_20240104_143022.docx`
|
||||
|
||||
2. **文件名验证**
|
||||
- 添加文件名格式验证
|
||||
- 限制特殊字符使用
|
||||
- 提供文件名建议
|
||||
|
||||
3. **批量上传**
|
||||
- 支持批量上传多个文件
|
||||
- 自动处理文件名冲突
|
||||
|
||||
4. **文件名搜索**
|
||||
- 支持按文件名搜索文档
|
||||
- 支持模糊匹配
|
||||
|
||||
## 总结
|
||||
|
||||
文件名保留功能确保了用户上传和下载文档时的一致性体验,特别是对中文文件名的完整支持,使得知识库系统更加符合中文用户的使用习惯。
|
||||
|
||||
所有功能都经过完整测试,确保在各种场景下都能正常工作。
|
||||
258
docs/MEILISEARCH_SETUP.md
Normal file
258
docs/MEILISEARCH_SETUP.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# Meilisearch 安装和配置指南
|
||||
|
||||
## 概述
|
||||
|
||||
本项目使用 Meilisearch 作为全文搜索引擎,为文档内容提供快速准确的搜索功能。
|
||||
|
||||
## 安装方式
|
||||
|
||||
### 方式 1:使用 Docker(推荐)
|
||||
|
||||
项目已经配置了 `docker-compose.yml` 文件,可以快速启动 Meilisearch 服务。
|
||||
|
||||
#### 启动服务
|
||||
|
||||
```bash
|
||||
# 启动 Meilisearch 服务
|
||||
docker-compose up -d meilisearch
|
||||
|
||||
# 查看服务状态
|
||||
docker-compose ps
|
||||
|
||||
# 查看服务日志
|
||||
docker-compose logs -f meilisearch
|
||||
```
|
||||
|
||||
#### 停止服务
|
||||
|
||||
```bash
|
||||
# 停止服务
|
||||
docker-compose down
|
||||
|
||||
# 停止服务并删除数据卷
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
### 方式 2:本地安装(macOS)
|
||||
|
||||
使用 Homebrew 安装:
|
||||
|
||||
```bash
|
||||
# 安装 Meilisearch
|
||||
brew install meilisearch
|
||||
|
||||
# 启动服务
|
||||
meilisearch --master-key="your-master-key-change-this-in-production"
|
||||
```
|
||||
|
||||
### 方式 3:本地安装(Linux)
|
||||
|
||||
```bash
|
||||
# 下载 Meilisearch
|
||||
curl -L https://install.meilisearch.com | sh
|
||||
|
||||
# 启动服务
|
||||
./meilisearch --master-key="your-master-key-change-this-in-production"
|
||||
```
|
||||
|
||||
### 方式 4:本地安装(Windows)
|
||||
|
||||
从 [Meilisearch 官方网站](https://www.meilisearch.com/docs/learn/getting_started/installation) 下载 Windows 版本,然后运行:
|
||||
|
||||
```powershell
|
||||
.\meilisearch.exe --master-key="your-master-key-change-this-in-production"
|
||||
```
|
||||
|
||||
## Laravel Scout 安装
|
||||
|
||||
本项目使用 Laravel Scout 作为搜索抽象层。
|
||||
|
||||
### 安装依赖包
|
||||
|
||||
```bash
|
||||
# 安装 Laravel Scout
|
||||
composer require laravel/scout
|
||||
|
||||
# 安装 Meilisearch PHP SDK
|
||||
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
|
||||
|
||||
# 发布 Scout 配置文件
|
||||
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
|
||||
```
|
||||
|
||||
安装完成后,会在 `config/scout.php` 中生成配置文件。
|
||||
|
||||
## 配置
|
||||
|
||||
### 主密钥(Master Key)
|
||||
|
||||
**重要**:在生产环境中,必须更改默认的主密钥!
|
||||
|
||||
1. 在 `docker-compose.yml` 中修改 `MEILI_MASTER_KEY` 环境变量
|
||||
2. 在 `.env` 文件中更新 `MEILISEARCH_KEY` 配置
|
||||
|
||||
### 环境变量
|
||||
|
||||
在 `.env` 文件中配置以下变量:
|
||||
|
||||
```env
|
||||
SCOUT_DRIVER=meilisearch
|
||||
MEILISEARCH_HOST=http://127.0.0.1:7700
|
||||
MEILISEARCH_KEY=your-master-key-change-this-in-production
|
||||
```
|
||||
|
||||
### Scout 索引配置
|
||||
|
||||
在 `config/scout.php` 中已配置 documents 索引的设置:
|
||||
|
||||
```php
|
||||
'meilisearch' => [
|
||||
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
|
||||
'key' => env('MEILISEARCH_KEY'),
|
||||
'index-settings' => [
|
||||
'documents' => [
|
||||
'filterableAttributes' => ['type', 'group_id', 'uploaded_by', 'conversion_status'],
|
||||
'sortableAttributes' => ['created_at', 'title', 'updated_at'],
|
||||
'searchableAttributes' => ['title', 'description', 'markdown_content'],
|
||||
'displayedAttributes' => ['id', 'title', 'description', 'type', 'group_id', 'uploaded_by', 'created_at', 'updated_at'],
|
||||
],
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
**配置说明**:
|
||||
- `filterableAttributes`: 可用于筛选的字段(类型、分组、上传者、转换状态)
|
||||
- `sortableAttributes`: 可用于排序的字段(创建时间、标题、更新时间)
|
||||
- `searchableAttributes`: 可搜索的字段(标题、描述、Markdown 内容)
|
||||
- `displayedAttributes`: 搜索结果中返回的字段
|
||||
|
||||
## 验证安装
|
||||
|
||||
访问 Meilisearch 管理界面:
|
||||
|
||||
```
|
||||
http://localhost:7700
|
||||
```
|
||||
|
||||
或使用 curl 测试:
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:7700/health'
|
||||
```
|
||||
|
||||
应该返回:
|
||||
|
||||
```json
|
||||
{"status":"available"}
|
||||
```
|
||||
|
||||
## 数据持久化
|
||||
|
||||
使用 Docker 方式时,Meilisearch 数据存储在 `storage/meilisearch` 目录中。
|
||||
|
||||
**注意**:请确保将此目录添加到 `.gitignore` 文件中,避免将索引数据提交到版本控制系统。
|
||||
|
||||
## 索引管理
|
||||
|
||||
### 查看所有索引
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:7700/indexes' \
|
||||
-H 'Authorization: Bearer your-master-key-change-this-in-production'
|
||||
```
|
||||
|
||||
### 删除索引
|
||||
|
||||
```bash
|
||||
curl -X DELETE 'http://localhost:7700/indexes/documents' \
|
||||
-H 'Authorization: Bearer your-master-key-change-this-in-production'
|
||||
```
|
||||
|
||||
### 重建索引
|
||||
|
||||
在 Laravel 项目中运行:
|
||||
|
||||
```bash
|
||||
# 清空所有索引
|
||||
php artisan scout:flush "App\Models\Document"
|
||||
|
||||
# 重新导入所有文档
|
||||
php artisan scout:import "App\Models\Document"
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 服务无法启动
|
||||
|
||||
1. 检查端口 7700 是否被占用:
|
||||
```bash
|
||||
lsof -i :7700
|
||||
```
|
||||
|
||||
2. 查看 Docker 日志:
|
||||
```bash
|
||||
docker-compose logs meilisearch
|
||||
```
|
||||
|
||||
### 搜索不返回结果
|
||||
|
||||
1. 检查文档是否已索引:
|
||||
```bash
|
||||
php artisan scout:import "App\Models\Document"
|
||||
```
|
||||
|
||||
2. 验证索引配置:
|
||||
```bash
|
||||
curl -X GET 'http://localhost:7700/indexes/documents/settings' \
|
||||
-H 'Authorization: Bearer your-master-key-change-this-in-production'
|
||||
```
|
||||
|
||||
### 权限错误
|
||||
|
||||
确保 `storage/meilisearch` 目录有正确的写入权限:
|
||||
|
||||
```bash
|
||||
chmod -R 775 storage/meilisearch
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 生产环境配置
|
||||
|
||||
在生产环境中,建议:
|
||||
|
||||
1. 使用强密钥作为 `MEILI_MASTER_KEY`
|
||||
2. 设置 `MEILI_ENV=production`
|
||||
3. 配置适当的资源限制(CPU、内存)
|
||||
4. 定期备份 `storage/meilisearch` 目录
|
||||
|
||||
### 索引优化
|
||||
|
||||
根据实际使用情况调整索引设置:
|
||||
|
||||
```php
|
||||
// config/scout.php
|
||||
'meilisearch' => [
|
||||
'index-settings' => [
|
||||
'documents' => [
|
||||
'filterableAttributes' => ['type', 'group_id', 'uploaded_by'],
|
||||
'sortableAttributes' => ['created_at', 'title'],
|
||||
'searchableAttributes' => ['title', 'description', 'markdown_content'],
|
||||
'rankingRules' => [
|
||||
'words',
|
||||
'typo',
|
||||
'proximity',
|
||||
'attribute',
|
||||
'sort',
|
||||
'exactness',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
## 更多信息
|
||||
|
||||
- [Meilisearch 官方文档](https://www.meilisearch.com/docs)
|
||||
- [Laravel Scout 文档](https://laravel.com/docs/scout)
|
||||
- [Meilisearch PHP SDK](https://github.com/meilisearch/meilisearch-php)
|
||||
304
docs/PROJECT_OVERVIEW.md
Normal file
304
docs/PROJECT_OVERVIEW.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# 知识库系统项目概览
|
||||
|
||||
## 项目简介
|
||||
|
||||
知识库系统是一个基于 Laravel 11 和 Filament 3.X 构建的企业级文档管理平台。系统支持 Word 文档上传、自动转换为 Markdown 格式、全文搜索、在线预览以及基于用户分组的细粒度权限控制。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 文档管理
|
||||
- **文档上传**:支持 .doc 和 .docx 格式的 Word 文档上传
|
||||
- **文档分类**:
|
||||
- 全局知识库:所有用户可访问
|
||||
- 专用知识库:仅特定分组用户可访问
|
||||
- **文档下载**:支持原始 Word 文档下载,并记录下载日志
|
||||
- **文档预览**:在线预览 Markdown 渲染后的文档内容
|
||||
|
||||
### 2. 自动文档转换
|
||||
- **异步转换**:使用队列系统异步将 Word 文档转换为 Markdown 格式
|
||||
- **转换工具**:支持 Pandoc 或 PHPWord 作为转换引擎
|
||||
- **状态跟踪**:实时跟踪转换状态(pending、processing、completed、failed)
|
||||
- **错误处理**:转换失败不影响文档的正常使用
|
||||
|
||||
### 3. 全文搜索
|
||||
- **搜索引擎**:集成 Meilisearch 提供快速的全文搜索
|
||||
- **搜索范围**:支持搜索文档标题、描述和 Markdown 内容
|
||||
- **权限过滤**:搜索结果自动过滤,只显示用户有权访问的文档
|
||||
- **高级筛选**:支持按文档类型、分组等条件筛选
|
||||
|
||||
### 4. 权限控制
|
||||
- **用户分组**:用户可以属于多个分组
|
||||
- **访问控制**:
|
||||
- 全局文档:所有用户可访问
|
||||
- 专用文档:只有所属分组的用户可访问
|
||||
- **权限验证**:在数据查询、下载、预览等操作中强制执行权限检查
|
||||
- **安全日志**:记录所有未授权访问尝试
|
||||
|
||||
### 5. 用户界面
|
||||
- **中文界面**:完整的简体中文用户界面
|
||||
- **管理面板**:基于 Filament 3.X 的现代化管理界面
|
||||
- **响应式设计**:支持桌面和移动设备访问
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 后端
|
||||
- **框架**:Laravel 11.x
|
||||
- **管理面板**:Filament 3.X
|
||||
- **数据库**:MySQL 8.0+ / PostgreSQL 13+
|
||||
- **搜索引擎**:Meilisearch 1.5+
|
||||
- **队列系统**:Redis Queue
|
||||
- **文档转换**:Pandoc 2.x+ 或 PHPWord
|
||||
|
||||
### 前端
|
||||
- **模板引擎**:Laravel Blade
|
||||
- **CSS 框架**:Tailwind CSS 3.x
|
||||
- **JavaScript**:Alpine.js 3.x(Filament 内置)
|
||||
|
||||
### 开发工具
|
||||
- **包管理**:Composer 2.x, npm
|
||||
- **测试框架**:Pest PHP
|
||||
- **代码质量**:PHPStan, Laravel Pint
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
knowledge-base-system/
|
||||
├── app/
|
||||
│ ├── Filament/ # Filament 资源和页面
|
||||
│ │ ├── Pages/ # 自定义页面(搜索页面)
|
||||
│ │ └── Resources/ # 资源管理(文档、分组、用户)
|
||||
│ ├── Http/
|
||||
│ │ └── Controllers/ # 控制器(文档预览)
|
||||
│ ├── Jobs/ # 队列任务(文档转换)
|
||||
│ ├── Models/ # Eloquent 模型
|
||||
│ ├── Observers/ # 模型观察者(文档索引)
|
||||
│ ├── Policies/ # 授权策略
|
||||
│ └── Services/ # 业务逻辑服务
|
||||
├── config/
|
||||
│ ├── documents.php # 文档转换配置
|
||||
│ ├── filesystems.php # 文件存储配置
|
||||
│ └── scout.php # Meilisearch 配置
|
||||
├── database/
|
||||
│ ├── factories/ # 测试数据工厂
|
||||
│ ├── migrations/ # 数据库迁移
|
||||
│ └── seeders/ # 数据填充
|
||||
├── docs/ # 项目文档
|
||||
├── resources/
|
||||
│ └── views/ # Blade 视图模板
|
||||
├── storage/
|
||||
│ └── app/
|
||||
│ └── private/
|
||||
│ ├── documents/ # 原始 Word 文档存储
|
||||
│ └── markdown/ # Markdown 文件存储
|
||||
├── tests/ # 测试文件
|
||||
└── .kiro/
|
||||
└── specs/ # 功能规格文档
|
||||
```
|
||||
|
||||
## 数据模型
|
||||
|
||||
### 核心实体
|
||||
|
||||
1. **User(用户)**
|
||||
- 系统用户
|
||||
- 可属于多个分组
|
||||
- 可上传文档
|
||||
|
||||
2. **Group(分组)**
|
||||
- 用户组织单位
|
||||
- 拥有专用知识库文档
|
||||
- 管理成员访问权限
|
||||
|
||||
3. **Document(文档)**
|
||||
- 文档记录
|
||||
- 包含原始文件和 Markdown 内容
|
||||
- 关联分组和上传者
|
||||
|
||||
4. **DownloadLog(下载日志)**
|
||||
- 记录文档下载历史
|
||||
- 用于审计和统计
|
||||
|
||||
### 关系图
|
||||
|
||||
```
|
||||
User ──┬── uploads ──> Document
|
||||
└── belongs_to ──> Group ──> owns ──> Document
|
||||
│
|
||||
└── has ──> DownloadLog
|
||||
```
|
||||
|
||||
## 已实现功能清单
|
||||
|
||||
### ✅ 核心功能
|
||||
- [x] 用户认证和授权
|
||||
- [x] 用户分组管理
|
||||
- [x] 文档上传和存储
|
||||
- [x] 文档分类(全局/专用)
|
||||
- [x] 基于分组的权限控制
|
||||
- [x] 文档下载和日志记录
|
||||
- [x] 文档搜索和筛选
|
||||
|
||||
### ✅ 高级功能
|
||||
- [x] Word 文档自动转换为 Markdown
|
||||
- [x] 异步队列处理转换任务
|
||||
- [x] Meilisearch 全文搜索集成
|
||||
- [x] 文档 Markdown 在线预览
|
||||
- [x] 搜索结果权限过滤
|
||||
- [x] 安全日志记录
|
||||
|
||||
### ✅ 用户界面
|
||||
- [x] Filament 管理面板
|
||||
- [x] 完整中文界面
|
||||
- [x] 文档管理界面
|
||||
- [x] 分组管理界面
|
||||
- [x] 用户管理界面
|
||||
- [x] 搜索页面
|
||||
- [x] 预览页面
|
||||
|
||||
### ⏳ 待完成功能
|
||||
- [ ] 属性基础测试(Property-Based Testing)
|
||||
- [ ] 完整的功能测试套件
|
||||
- [ ] 性能优化(缓存、索引优化)
|
||||
- [ ] UI 增强(Alpine.js 动画和交互)
|
||||
- [ ] 部署文档和脚本
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 环境要求
|
||||
- PHP 8.1+
|
||||
- Composer 2.x
|
||||
- Node.js 18+
|
||||
- MySQL 8.0+ 或 PostgreSQL 13+
|
||||
- Redis 6.0+
|
||||
- Meilisearch 1.5+
|
||||
- Pandoc 2.x+(可选)
|
||||
|
||||
### 安装步骤
|
||||
|
||||
1. **克隆项目**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd knowledge-base-system
|
||||
```
|
||||
|
||||
2. **安装依赖**
|
||||
```bash
|
||||
composer install
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **配置环境**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
4. **配置数据库**
|
||||
编辑 `.env` 文件,设置数据库连接信息
|
||||
|
||||
5. **运行迁移**
|
||||
```bash
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
6. **生成测试数据**(可选)
|
||||
```bash
|
||||
php artisan db:seed
|
||||
```
|
||||
|
||||
7. **启动服务**
|
||||
```bash
|
||||
# 启动 Laravel 开发服务器
|
||||
php artisan serve
|
||||
|
||||
# 启动队列工作进程
|
||||
php artisan queue:work
|
||||
|
||||
# 启动 Meilisearch
|
||||
meilisearch --master-key="your-master-key"
|
||||
```
|
||||
|
||||
8. **访问系统**
|
||||
打开浏览器访问 `http://localhost:8000/admin`
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 文档转换配置
|
||||
|
||||
在 `.env` 文件中配置:
|
||||
|
||||
```env
|
||||
DOCUMENT_CONVERSION_DRIVER=pandoc
|
||||
PANDOC_PATH=/usr/local/bin/pandoc
|
||||
CONVERSION_TIMEOUT=300
|
||||
```
|
||||
|
||||
### Meilisearch 配置
|
||||
|
||||
```env
|
||||
MEILISEARCH_HOST=http://127.0.0.1:7700
|
||||
MEILISEARCH_KEY=your-master-key
|
||||
SCOUT_DRIVER=meilisearch
|
||||
```
|
||||
|
||||
### 队列配置
|
||||
|
||||
```env
|
||||
QUEUE_CONNECTION=redis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
php artisan test
|
||||
|
||||
# 运行特定测试
|
||||
php artisan test --filter=DocumentAccessScopePropertyTest
|
||||
|
||||
# 生成测试覆盖率报告
|
||||
php artisan test --coverage
|
||||
```
|
||||
|
||||
### 测试数据
|
||||
|
||||
使用 Factory 生成测试数据:
|
||||
|
||||
```php
|
||||
// 创建用户
|
||||
$user = User::factory()->create();
|
||||
|
||||
// 创建分组
|
||||
$group = Group::factory()->create();
|
||||
|
||||
// 创建文档
|
||||
$document = Document::factory()->create([
|
||||
'type' => 'global',
|
||||
]);
|
||||
```
|
||||
|
||||
## 部署
|
||||
|
||||
详细的部署指南请参考 [DEPLOYMENT.md](./DEPLOYMENT.md)
|
||||
|
||||
## 贡献指南
|
||||
|
||||
请参考 [CONTRIBUTING.md](./CONTRIBUTING.md)
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目采用 MIT 许可证
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请联系项目维护者。
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2025-12-05
|
||||
**版本**:1.0.0
|
||||
130
docs/SETUP.md
Normal file
130
docs/SETUP.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# 知识库系统 - 安装配置文档
|
||||
|
||||
## 项目初始化完成
|
||||
|
||||
本项目已成功完成初始化,包括以下配置:
|
||||
|
||||
### 1. Laravel 框架
|
||||
- **版本**: Laravel 12.x
|
||||
- **PHP 版本要求**: PHP 8.1+
|
||||
- **数据库**: SQLite (database/database.sqlite)
|
||||
|
||||
### 2. Filament 管理面板
|
||||
- **版本**: Filament 3.3.45
|
||||
- **面板 ID**: admin
|
||||
- **访问路径**: `/admin`
|
||||
- **登录路径**: `/admin/login`
|
||||
|
||||
### 3. 中文化配置
|
||||
- **应用语言**: zh_CN (简体中文)
|
||||
- **Laravel 语言包**: laravel-lang/common ^6.7
|
||||
- **Filament 中文翻译**: 已内置并发布
|
||||
|
||||
### 4. 管理员账户
|
||||
- **用户名**: admin
|
||||
- **邮箱**: admin@example.com
|
||||
- **密码**: (创建时设置)
|
||||
|
||||
## 环境配置
|
||||
|
||||
### 应用配置 (.env)
|
||||
```env
|
||||
APP_LOCALE=zh_CN
|
||||
APP_FALLBACK_LOCALE=zh_CN
|
||||
APP_FAKER_LOCALE=zh_CN
|
||||
```
|
||||
|
||||
### 数据库配置
|
||||
```env
|
||||
DB_CONNECTION=sqlite
|
||||
```
|
||||
|
||||
## 已安装的主要依赖
|
||||
|
||||
### 生产依赖
|
||||
- filament/filament: ^3.0
|
||||
- livewire/livewire: ^3.7
|
||||
- blade-ui-kit/blade-heroicons: ^2.6
|
||||
- doctrine/dbal: ^4.4
|
||||
|
||||
### 开发依赖
|
||||
- laravel-lang/common: ^6.7
|
||||
- phpunit/phpunit: ^11.5
|
||||
- laravel/pint: ^1.26
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
.
|
||||
├── app/
|
||||
│ ├── Filament/
|
||||
│ │ ├── Resources/ # Filament 资源文件
|
||||
│ │ ├── Pages/ # Filament 页面
|
||||
│ │ └── Widgets/ # Filament 小部件
|
||||
│ ├── Models/ # Eloquent 模型
|
||||
│ └── Providers/
|
||||
│ └── Filament/
|
||||
│ └── AdminPanelProvider.php # Filament 面板配置
|
||||
├── database/
|
||||
│ ├── database.sqlite # SQLite 数据库文件
|
||||
│ ├── migrations/ # 数据库迁移
|
||||
│ └── factories/ # 模型工厂
|
||||
├── lang/
|
||||
│ ├── zh_CN/ # Laravel 中文语言包
|
||||
│ └── vendor/
|
||||
│ └── filament/
|
||||
│ └── zh_CN/ # Filament 中文翻译
|
||||
├── tests/
|
||||
│ └── Feature/
|
||||
│ └── SetupTest.php # 配置验证测试
|
||||
└── .env # 环境配置文件
|
||||
```
|
||||
|
||||
## 验证安装
|
||||
|
||||
运行以下命令验证安装是否成功:
|
||||
|
||||
```bash
|
||||
# 运行配置测试
|
||||
php artisan test --filter=SetupTest
|
||||
|
||||
# 清除缓存
|
||||
php artisan config:clear
|
||||
php artisan route:clear
|
||||
php artisan view:clear
|
||||
|
||||
# 查看路由
|
||||
php artisan route:list --path=admin
|
||||
```
|
||||
|
||||
## 启动开发服务器
|
||||
|
||||
```bash
|
||||
php artisan serve
|
||||
```
|
||||
|
||||
然后访问 http://localhost:8000/admin/login 登录管理面板。
|
||||
|
||||
## 下一步
|
||||
|
||||
根据 `.kiro/specs/knowledge-base-system/tasks.md` 中的任务列表,接下来需要:
|
||||
|
||||
1. 创建数据库迁移和模型
|
||||
2. 实现文档权限查询作用域
|
||||
3. 创建服务类和策略类
|
||||
4. 创建 Filament 资源
|
||||
|
||||
## 测试结果
|
||||
|
||||
所有初始化测试均已通过:
|
||||
- ✓ 应用语言配置为简体中文
|
||||
- ✓ 数据库连接正常
|
||||
- ✓ Filament 管理面板路由可访问
|
||||
- ✓ 中文翻译文件存在
|
||||
|
||||
## 技术支持
|
||||
|
||||
如有问题,请参考:
|
||||
- Laravel 文档: https://laravel.com/docs
|
||||
- Filament 文档: https://filamentphp.com/docs
|
||||
- Laravel Lang 文档: https://laravel-lang.com/
|
||||
189
docs/security-logging.md
Normal file
189
docs/security-logging.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# 安全日志记录
|
||||
|
||||
## 概述
|
||||
|
||||
知识库系统实现了全面的安全日志记录功能,用于记录所有未授权访问尝试和安全相关事件。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 自动记录未授权访问
|
||||
|
||||
系统会自动记录以下未授权访问尝试:
|
||||
|
||||
- **查看文档** (view):用户尝试查看无权访问的专用文档
|
||||
- **下载文档** (download):用户尝试下载无权访问的文档
|
||||
- **更新文档** (update):用户尝试更新不属于自己的文档
|
||||
- **删除文档** (delete):用户尝试删除不属于自己的文档
|
||||
- **恢复文档** (restore):用户尝试恢复不属于自己的文档
|
||||
- **永久删除** (forceDelete):用户尝试永久删除不属于自己的文档
|
||||
|
||||
### 2. 记录的信息
|
||||
|
||||
每条安全日志包含以下信息:
|
||||
|
||||
- **事件类型** (event):unauthorized_access
|
||||
- **操作类型** (action):view, download, update, delete 等
|
||||
- **用户信息**:
|
||||
- 用户 ID (user_id)
|
||||
- 用户名 (user_name)
|
||||
- 用户邮箱 (user_email)
|
||||
- **文档信息**:
|
||||
- 文档 ID (document_id)
|
||||
- 文档标题 (document_title)
|
||||
- 文档类型 (document_type)
|
||||
- 文档分组 ID (document_group_id)
|
||||
- **请求信息**:
|
||||
- IP 地址 (ip_address)
|
||||
- 时间戳 (timestamp)
|
||||
- 用户代理 (user_agent)
|
||||
|
||||
## 日志配置
|
||||
|
||||
### 日志通道
|
||||
|
||||
安全日志使用独立的 `security` 通道,配置在 `config/logging.php` 中:
|
||||
|
||||
```php
|
||||
'security' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/security.log'),
|
||||
'level' => env('LOG_LEVEL', 'info'),
|
||||
'days' => env('LOG_SECURITY_DAYS', 90),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
```
|
||||
|
||||
### 日志保留期
|
||||
|
||||
默认情况下,安全日志保留 90 天。可以通过环境变量 `LOG_SECURITY_DAYS` 调整:
|
||||
|
||||
```env
|
||||
LOG_SECURITY_DAYS=90
|
||||
```
|
||||
|
||||
## 查看日志
|
||||
|
||||
### 日志文件位置
|
||||
|
||||
安全日志存储在:`storage/logs/security.log`
|
||||
|
||||
每天会自动创建新的日志文件,格式为:`security-YYYY-MM-DD.log`
|
||||
|
||||
### 日志格式示例
|
||||
|
||||
```
|
||||
[2024-12-04 10:30:45] local.WARNING: 未授权访问尝试 {"event":"unauthorized_access","action":"view","user_id":2,"user_name":"张三","user_email":"zhangsan@example.com","document_id":5,"document_title":"机密文档","document_type":"dedicated","document_group_id":3,"ip_address":"192.168.1.100","timestamp":"2024-12-04T10:30:45+08:00","user_agent":"Mozilla/5.0..."}
|
||||
```
|
||||
|
||||
### 使用命令行查看日志
|
||||
|
||||
查看最新的安全日志:
|
||||
|
||||
```bash
|
||||
tail -f storage/logs/security.log
|
||||
```
|
||||
|
||||
查看今天的安全日志:
|
||||
|
||||
```bash
|
||||
cat storage/logs/security-$(date +%Y-%m-%d).log
|
||||
```
|
||||
|
||||
搜索特定用户的未授权访问:
|
||||
|
||||
```bash
|
||||
grep "user_id\":2" storage/logs/security.log
|
||||
```
|
||||
|
||||
搜索特定文档的访问尝试:
|
||||
|
||||
```bash
|
||||
grep "document_id\":5" storage/logs/security.log
|
||||
```
|
||||
|
||||
## 安全监控建议
|
||||
|
||||
### 1. 定期审查
|
||||
|
||||
建议定期审查安全日志,特别关注:
|
||||
|
||||
- 频繁的未授权访问尝试
|
||||
- 来自异常 IP 地址的访问
|
||||
- 针对敏感文档的访问尝试
|
||||
- 同一用户的大量失败尝试
|
||||
|
||||
### 2. 告警设置
|
||||
|
||||
可以配置日志监控工具(如 ELK Stack、Graylog 等)来:
|
||||
|
||||
- 实时监控安全日志
|
||||
- 设置告警规则
|
||||
- 生成安全报告
|
||||
- 可视化安全事件
|
||||
|
||||
### 3. 日志分析
|
||||
|
||||
使用日志分析工具可以:
|
||||
|
||||
- 识别攻击模式
|
||||
- 发现潜在的安全威胁
|
||||
- 追踪用户行为
|
||||
- 生成合规报告
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### SecurityLogger 服务
|
||||
|
||||
系统提供了 `SecurityLogger` 服务类,可以用于记录其他安全事件:
|
||||
|
||||
```php
|
||||
use App\Services\SecurityLogger;
|
||||
|
||||
// 记录未授权访问
|
||||
$securityLogger->logUnauthorizedAccess($user, $document, 'view');
|
||||
|
||||
// 记录权限验证失败
|
||||
$securityLogger->logAuthorizationFailure($user, 'Document', $documentId, 'view', '用户不在分组中');
|
||||
|
||||
// 记录可疑活动
|
||||
$securityLogger->logSuspiciousActivity($user, '短时间内大量下载尝试', [
|
||||
'attempts' => 50,
|
||||
'timeframe' => '5分钟'
|
||||
]);
|
||||
```
|
||||
|
||||
## 合规性
|
||||
|
||||
安全日志记录有助于满足以下合规要求:
|
||||
|
||||
- **数据保护法规**:记录数据访问和使用情况
|
||||
- **审计要求**:提供完整的访问审计轨迹
|
||||
- **安全标准**:符合 ISO 27001 等安全标准
|
||||
- **内部政策**:支持组织的安全政策执行
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **隐私保护**:日志中包含用户信息,需要妥善保管
|
||||
2. **存储空间**:定期清理旧日志以节省存储空间
|
||||
3. **性能影响**:日志记录对性能影响很小,但在高并发场景下需要监控
|
||||
4. **日志轮转**:使用 daily 驱动自动进行日志轮转
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 日志未生成
|
||||
|
||||
1. 检查 `storage/logs` 目录权限
|
||||
2. 确认日志配置正确
|
||||
3. 检查 `LOG_CHANNEL` 环境变量
|
||||
|
||||
### 日志文件过大
|
||||
|
||||
1. 调整 `LOG_SECURITY_DAYS` 减少保留天数
|
||||
2. 配置日志轮转策略
|
||||
3. 使用外部日志管理系统
|
||||
|
||||
### 无法写入日志
|
||||
|
||||
1. 检查文件系统权限
|
||||
2. 确认磁盘空间充足
|
||||
3. 检查 SELinux 或 AppArmor 配置
|
||||
Reference in New Issue
Block a user