# 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
