- 添加 Dockerfile 与多套 docker-compose 配置(开发/生产环境) - 集成 Laravel Octane (Swoole) 提升性能 - 新增健康检查、监控脚本及部署文档 - 新增 Docker 镜像离线导入包(MySQL/Redis/Meilisearch) - 优化文档转换、预览服务及队列任务 - 添加 CreateAdminUser 命令与路由健康检查接口 - 新增 Swoole 队列兼容性测试套件 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
179 lines
4.9 KiB
PHP
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);
|
|
}
|
|
}
|
|
|