فهرست منبع

queue analysis report generation and unify JSON responses

Move analysis report generation flow fully into the pdf queue and return a consistent JSON payload with short enqueue idempotency protection for duplicate requests.

Made-with: Cursor
yemeishu 2 هفته پیش
والد
کامیت
0a45647a71

+ 68 - 11
app/Http/Controllers/ExamAnalysisPdfController.php

@@ -2,32 +2,89 @@
 
 namespace App\Http\Controllers;
 
-use App\Services\ExamPdfExportService;
+use App\Jobs\GenerateAnalysisPdfJob;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 class ExamAnalysisPdfController extends Controller
 {
-    public function show(Request $request, ExamPdfExportService $pdfExportService)
+    private const ENQUEUE_LOCK_TTL_SECONDS = 120;
+
+    public function show(Request $request)
     {
         $paperId = $request->query('paperId');
         $studentId = $request->query('studentId');
         $recordId = $request->query('recordId'); // 可选的OCR记录ID
 
         if (!$paperId || !$studentId) {
-            return response('paperId 和 studentId 不能为空', 400);
+            return response()->json([
+                'success' => false,
+                'message' => 'paperId 和 studentId 不能为空',
+            ], 400);
         }
 
-        $pdfUrl = $pdfExportService->generateAnalysisReportPdf($paperId, $studentId, $recordId);
-        if (!$pdfUrl) {
-            Log::error('ExamAnalysisPdfController: 学情报告生成失败', [
-                'paper_id' => $paperId,
-                'student_id' => $studentId,
-                'record_id' => $recordId,
+        // 已生成则直接返回链接
+        $existingPdfUrl = DB::connection('mysql')
+            ->table('exam_analysis_results')
+            ->where('paper_id', $paperId)
+            ->where('student_id', $studentId)
+            ->whereNotNull('analysis_pdf_url')
+            ->where('analysis_pdf_url', '!=', '')
+            ->orderByDesc('updated_at')
+            ->value('analysis_pdf_url');
+
+        if ($existingPdfUrl) {
+            return response()->json([
+                'success' => true,
+                'message' => '学情报告已生成',
+                'data' => [
+                    'paper_id' => $paperId,
+                    'student_id' => $studentId,
+                    'record_id' => $recordId,
+                    'status' => 'completed',
+                    'pdf_url' => $existingPdfUrl,
+                    'queued' => false,
+                ],
             ]);
-            return response('生成学情报告失败,请稍后重试', 500);
         }
 
-        return redirect($pdfUrl);
+        $lockKey = sprintf('analysis_pdf:enqueue:%s:%s', $paperId, $studentId);
+        $acquired = Cache::add($lockKey, now()->timestamp, now()->addSeconds(self::ENQUEUE_LOCK_TTL_SECONDS));
+        if (! $acquired) {
+            return response()->json([
+                'success' => true,
+                'message' => '学情报告正在生成中,请稍后重试',
+                'data' => [
+                    'paper_id' => $paperId,
+                    'student_id' => $studentId,
+                    'record_id' => $recordId,
+                    'status' => 'processing',
+                    'pdf_url' => null,
+                    'queued' => false,
+                ],
+            ], 202);
+        }
+
+        dispatch(new GenerateAnalysisPdfJob($paperId, $studentId, $recordId));
+        Log::info('ExamAnalysisPdfController: 学情报告生成任务已入队', [
+            'paper_id' => $paperId,
+            'student_id' => $studentId,
+            'record_id' => $recordId,
+        ]);
+
+        return response()->json([
+            'success' => true,
+            'message' => '学情报告任务已入队,正在后台生成',
+            'data' => [
+                'paper_id' => $paperId,
+                'student_id' => $studentId,
+                'record_id' => $recordId,
+                'status' => 'processing',
+                'pdf_url' => null,
+                'queued' => true,
+            ],
+        ], 202);
     }
 }

+ 47 - 0
app/Jobs/ProcessAnalysisReportTaskJob.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Services\ExamAnalysisService;
+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;
+
+class ProcessAnalysisReportTaskJob implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    public int $tries = 3;
+
+    public function __construct(
+        public string $taskId,
+        public string $paperId,
+        public string $studentId,
+        public ?string $recordId = null
+    ) {
+        // 与 PDF 相关重流程统一走 pdf 队列
+        $this->onQueue('pdf');
+    }
+
+    public function handle(ExamAnalysisService $examAnalysisService): void
+    {
+        Log::info('ProcessAnalysisReportTaskJob: 开始处理学情报告任务', [
+            'task_id' => $this->taskId,
+            'paper_id' => $this->paperId,
+            'student_id' => $this->studentId,
+            'record_id' => $this->recordId,
+            'attempt' => $this->attempts(),
+        ]);
+
+        $examAnalysisService->processReportGenerationTask(
+            $this->taskId,
+            $this->paperId,
+            $this->studentId,
+            $this->recordId
+        );
+    }
+}
+

+ 9 - 5
app/Services/ExamAnalysisService.php

@@ -4,6 +4,7 @@ namespace App\Services;
 
 use App\DTO\ExamAnalysisDataDto;
 use App\DTO\ReportPayloadDto;
+use App\Jobs\ProcessAnalysisReportTaskJob;
 use App\Models\Paper;
 use App\Models\PaperQuestion;
 use App\Models\Student;
@@ -43,10 +44,13 @@ class ExamAnalysisService
             'record_id' => $recordId,
         ]);
 
-        // 触发后台处理(实际项目中应使用队列)
-        // dispatch(new AnalysisReportJob($taskId));
-        // 目前使用同步调用模拟异步
-        $this->processReportGeneration($taskId, $paperId, $studentId, $recordId);
+        // 复杂流程改为统一进入 pdf 队列异步处理
+        dispatch(new ProcessAnalysisReportTaskJob(
+            $taskId,
+            $paperId,
+            $studentId,
+            $recordId
+        ));
 
         return $taskId;
     }
@@ -114,7 +118,7 @@ class ExamAnalysisService
     /**
      * 处理报告生成(后台任务)
      */
-    private function processReportGeneration(string $taskId, string $paperId, string $studentId, ?string $recordId): void
+    public function processReportGenerationTask(string $taskId, string $paperId, string $studentId, ?string $recordId): void
     {
         try {
             $this->taskManager->updateTaskProgress($taskId, 10, '正在获取分析数据...');