- 实现基于 Laravel 11 和 Filament 3.X 的文档管理系统 - 添加用户认证和分组管理功能 - 实现文档上传、分类和权限控制 - 集成 Word 文档自动转换为 Markdown - 集成 Meilisearch 全文搜索引擎 - 实现文档在线预览功能 - 添加安全日志和审计功能 - 完整的简体中文界面 - 包含完整的项目文档和部署指南 技术栈: - Laravel 11.x - Filament 3.X - Meilisearch 1.5+ - Pandoc 文档转换 - Redis 队列系统 - Pest PHP 测试框架
178 lines
4.8 KiB
PHP
178 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\Document;
|
|
use App\Services\DocumentConversionService;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* 文档转换为 Markdown 的队列任务
|
|
*/
|
|
class ConvertDocumentToMarkdown implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
/**
|
|
* 任务最大尝试次数
|
|
*
|
|
* @var int
|
|
*/
|
|
public $tries;
|
|
|
|
/**
|
|
* 任务超时时间(秒)
|
|
*
|
|
* @var int
|
|
*/
|
|
public $timeout;
|
|
|
|
/**
|
|
* 重试延迟(秒)
|
|
*
|
|
* @var int
|
|
*/
|
|
public $backoff;
|
|
|
|
/**
|
|
* 文档实例
|
|
*
|
|
* @var Document
|
|
*/
|
|
protected Document $document;
|
|
|
|
/**
|
|
* 创建新的任务实例
|
|
*
|
|
* @param Document $document
|
|
*/
|
|
public function __construct(Document $document)
|
|
{
|
|
$this->document = $document;
|
|
$this->tries = config('documents.conversion.retry_times', 3);
|
|
$this->timeout = config('documents.conversion.timeout', 300);
|
|
$this->backoff = config('documents.conversion.retry_delay', 60);
|
|
}
|
|
|
|
/**
|
|
* 执行任务
|
|
*
|
|
* @param DocumentConversionService $conversionService
|
|
* @return void
|
|
*/
|
|
public function handle(DocumentConversionService $conversionService): void
|
|
{
|
|
try {
|
|
Log::info('开始转换文档', [
|
|
'document_id' => $this->document->id,
|
|
'document_title' => $this->document->title,
|
|
'attempt' => $this->attempts(),
|
|
]);
|
|
|
|
// 转换文档为 Markdown
|
|
$result = $conversionService->convertToMarkdown($this->document);
|
|
$markdown = $result['markdown'];
|
|
$mediaDir = $result['mediaDir'] ?? null;
|
|
$tempDir = $result['tempDir'];
|
|
|
|
try {
|
|
// 保存 Markdown 文件和媒体文件
|
|
$markdownPath = $conversionService->saveMarkdownToFile($this->document, $markdown, $mediaDir);
|
|
|
|
// 更新文档的 Markdown 信息
|
|
$conversionService->updateDocumentMarkdown($this->document, $markdownPath);
|
|
} finally {
|
|
// 清理临时目录
|
|
if (isset($tempDir) && file_exists($tempDir)) {
|
|
$this->deleteDirectory($tempDir);
|
|
}
|
|
}
|
|
|
|
Log::info('文档转换成功', [
|
|
'document_id' => $this->document->id,
|
|
'document_title' => $this->document->title,
|
|
'markdown_path' => $markdownPath,
|
|
]);
|
|
|
|
// 转换成功后,触发索引(如果需要)
|
|
// 这将在后续任务中实现
|
|
// $this->document->searchable();
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('文档转换失败', [
|
|
'document_id' => $this->document->id,
|
|
'document_title' => $this->document->title,
|
|
'attempt' => $this->attempts(),
|
|
'error' => $e->getMessage(),
|
|
'trace' => $e->getTraceAsString(),
|
|
]);
|
|
|
|
// 如果已达到最大重试次数,标记为失败
|
|
if ($this->attempts() >= $this->tries) {
|
|
$conversionService->handleConversionFailure($this->document, $e);
|
|
}
|
|
|
|
// 重新抛出异常以触发重试
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 任务失败时的处理
|
|
*
|
|
* @param \Throwable $exception
|
|
* @return void
|
|
*/
|
|
public function failed(\Throwable $exception): void
|
|
{
|
|
Log::error('文档转换任务最终失败', [
|
|
'document_id' => $this->document->id,
|
|
'document_title' => $this->document->title,
|
|
'error' => $exception->getMessage(),
|
|
]);
|
|
|
|
// 确保文档状态被标记为失败
|
|
$conversionService = app(DocumentConversionService::class);
|
|
$conversionService->handleConversionFailure(
|
|
$this->document,
|
|
$exception instanceof \Exception ? $exception : new \Exception($exception->getMessage())
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 递归删除目录
|
|
*
|
|
* @param string $dir 目录路径
|
|
* @return void
|
|
*/
|
|
protected function deleteDirectory(string $dir): void
|
|
{
|
|
if (!file_exists($dir)) {
|
|
return;
|
|
}
|
|
|
|
if (!is_dir($dir)) {
|
|
unlink($dir);
|
|
return;
|
|
}
|
|
|
|
$files = array_diff(scandir($dir), ['.', '..']);
|
|
foreach ($files as $file) {
|
|
$path = $dir . '/' . $file;
|
|
if (is_dir($path)) {
|
|
$this->deleteDirectory($path);
|
|
} else {
|
|
unlink($path);
|
|
}
|
|
}
|
|
|
|
rmdir($dir);
|
|
}
|
|
}
|
|
|