GenerateExamPdfJob.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. <?php
  2. namespace App\Jobs;
  3. use App\Models\Paper;
  4. use App\Services\ExamPdfExportService;
  5. use App\Services\QuestionBankService;
  6. use App\Services\PaperPayloadService;
  7. use App\Services\TaskManager;
  8. use Illuminate\Bus\Queueable;
  9. use Illuminate\Contracts\Queue\ShouldQueue;
  10. use Illuminate\Foundation\Bus\Dispatchable;
  11. use Illuminate\Queue\InteractsWithQueue;
  12. use Illuminate\Queue\SerializesModels;
  13. use Illuminate\Support\Facades\Log;
  14. class GenerateExamPdfJob implements ShouldQueue
  15. {
  16. use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  17. public string $taskId;
  18. public string $paperId;
  19. public int $maxAttempts = 3;
  20. public function __construct(string $taskId, string $paperId)
  21. {
  22. $this->taskId = $taskId;
  23. $this->paperId = $paperId;
  24. // 指定使用 pdf 队列,由独立的 pdf-worker 容器处理
  25. $this->onQueue('pdf');
  26. }
  27. public function handle(
  28. ExamPdfExportService $pdfExportService,
  29. QuestionBankService $questionBankService,
  30. PaperPayloadService $paperPayloadService,
  31. TaskManager $taskManager
  32. ): void {
  33. try {
  34. Log::info('开始处理PDF生成队列任务', [
  35. 'task_id' => $this->taskId,
  36. 'paper_id' => $this->paperId,
  37. 'attempt' => $this->attempts(),
  38. ]);
  39. // 【修复】首先检查试卷是否存在
  40. $paperModel = Paper::with('questions')->find($this->paperId);
  41. if (!$paperModel) {
  42. Log::error('PDF生成队列任务失败:试卷不存在', [
  43. 'task_id' => $this->taskId,
  44. 'paper_id' => $this->paperId,
  45. 'attempt' => $this->attempts(),
  46. ]);
  47. // 如果试卷不存在,判断是否需要重试
  48. if ($this->attempts() < $this->maxAttempts) {
  49. Log::info('试卷不存在,将在2秒后重试', [
  50. 'task_id' => $this->taskId,
  51. 'paper_id' => $this->paperId,
  52. 'attempt' => $this->attempts(),
  53. 'next_attempt' => $this->attempts() + 1,
  54. ]);
  55. // 延迟2秒后重试(缩短间隔,减少对回调的影响)
  56. $this->release(2);
  57. return;
  58. } else {
  59. Log::error('试卷不存在且已达到最大重试次数,标记任务失败', [
  60. 'task_id' => $this->taskId,
  61. 'paper_id' => $this->paperId,
  62. 'attempts' => $this->attempts(),
  63. ]);
  64. $taskManager->markTaskFailed($this->taskId, "试卷不存在: {$this->paperId}");
  65. return;
  66. }
  67. }
  68. // 检查试卷是否有题目
  69. if ($paperModel->questions->isEmpty()) {
  70. Log::error('PDF生成队列任务失败:试卷没有题目数据', [
  71. 'task_id' => $this->taskId,
  72. 'paper_id' => $this->paperId,
  73. 'question_count' => 0,
  74. ]);
  75. if ($this->attempts() < $this->maxAttempts) {
  76. Log::info('试卷没有题目,将在1秒后重试', [
  77. 'task_id' => $this->taskId,
  78. 'paper_id' => $this->paperId,
  79. 'attempt' => $this->attempts(),
  80. ]);
  81. // 延迟1秒后重试(更短间隔)
  82. $this->release(1);
  83. return;
  84. } else {
  85. $taskManager->markTaskFailed($this->taskId, "试卷没有题目数据: {$this->paperId}");
  86. return;
  87. }
  88. }
  89. $taskManager->updateTaskProgress($this->taskId, 10, '开始生成统一PDF(直接合并两个页面,效率最高)...');
  90. // 【终极优化】直接合并两个HTML页面生成一份PDF(无需生成单独PDF)
  91. $unifiedPdfUrl = $pdfExportService->generateUnifiedPdf($this->paperId);
  92. $taskManager->updateTaskProgress($this->taskId, 90, 'PDF生成完成,准备返回结果...');
  93. $examContent = $paperPayloadService->buildExamContent($paperModel);
  94. // 标记任务完成(完整PDF存储到all_pdf_url字段)
  95. $taskManager->markTaskCompleted($this->taskId, [
  96. 'exam_content' => $examContent,
  97. 'pdfs' => [
  98. 'all_pdf' => $unifiedPdfUrl, // 【完整PDF】包含试卷和判卷,存储到all_pdf_url字段
  99. ],
  100. ]);
  101. Log::info('PDF生成队列任务完成(终极优化:直接合并HTML生成一份完整PDF)', [
  102. 'task_id' => $this->taskId,
  103. 'paper_id' => $this->paperId,
  104. 'all_pdf_url' => $unifiedPdfUrl,
  105. 'question_count' => $paperModel->questions->count(),
  106. 'method' => 'generateUnifiedPdf (direct merge, fastest)',
  107. ]);
  108. // 发送回调通知(在合并PDF完成后)
  109. $taskManager->sendCallback($this->taskId);
  110. } catch (\Exception $e) {
  111. Log::error('PDF生成队列任务失败', [
  112. 'task_id' => $this->taskId,
  113. 'paper_id' => $this->paperId,
  114. 'error' => $e->getMessage(),
  115. 'trace' => $e->getTraceAsString(),
  116. ]);
  117. // 如果是第一次失败且试卷可能还在创建中,等待后重试
  118. if ($this->attempts() < $this->maxAttempts && strpos($e->getMessage(), '不存在') !== false) {
  119. Log::info('检测到试卷不存在错误,将在2秒后重试', [
  120. 'task_id' => $this->taskId,
  121. 'paper_id' => $this->paperId,
  122. 'attempt' => $this->attempts(),
  123. ]);
  124. $this->release(2);
  125. return;
  126. }
  127. $taskManager->markTaskFailed($this->taskId, $e->getMessage());
  128. }
  129. }
  130. }