Files
KnowledgeBase/app/Jobs/ConvertDocumentToMarkdown.php
lizhuoran 3c206e9e06 feat: 新增 Docker 部署支持、Swoole/Octane 集成及相关优化
- 添加 Dockerfile 与多套 docker-compose 配置(开发/生产环境)
- 集成 Laravel Octane (Swoole) 提升性能
- 新增健康检查、监控脚本及部署文档
- 新增 Docker 镜像离线导入包(MySQL/Redis/Meilisearch)
- 优化文档转换、预览服务及队列任务
- 添加 CreateAdminUser 命令与路由健康检查接口
- 新增 Swoole 队列兼容性测试套件

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:51:19 +08:00

179 lines
4.9 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'];
$tempDirName = $result['tempDirName'];
try {
// 保存 Markdown 文件和媒体文件
$markdownPath = $conversionService->saveMarkdownToFile($this->document, $markdown, $mediaDir);
// 更新文档的 Markdown 信息
$conversionService->updateDocumentMarkdown($this->document, $markdownPath);
} finally {
// 清理临时目录
if (isset($tempDirName) && \Storage::disk('local')->exists($tempDirName)) {
\Storage::disk('local')->deleteDirectory($tempDirName);
}
}
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);
}
}