Explorar el Código

fix: harden knowledge callback persistence and payload urls

Add duplicate-primary fallback when saving knowledge explanations and include top-level callback URL fields with payload debug logging for easier downstream integration.

Co-authored-by: Cursor <cursoragent@cursor.com>
yemeishu hace 5 días
padre
commit
99a57acf2c
Se han modificado 2 ficheros con 76 adiciones y 4 borrados
  1. 58 4
      app/Services/KnowledgeExplanationService.php
  2. 18 0
      app/Services/TaskManager.php

+ 58 - 4
app/Services/KnowledgeExplanationService.php

@@ -7,7 +7,9 @@ use App\Models\KnowledgePoint;
 use App\Models\MistakeRecord;
 use App\Models\PaperQuestion;
 use App\Models\Question;
+use Illuminate\Database\QueryException;
 use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
 
 class KnowledgeExplanationService
 {
@@ -77,9 +79,7 @@ class KnowledgeExplanationService
             'case_payload' => $casePayload,
         ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
 
-        $record = KnowledgeExplanation::updateOrCreate([
-            'knowledge_id' => $knowledgeId,
-        ], [
+        $recordPayload = [
             'teacher_id' => $teacherId,
             'student_id' => $studentId,
             'assemble_type' => 22,
@@ -89,7 +89,22 @@ class KnowledgeExplanationService
             'content_hash' => $contentHash,
             'pdf_url' => null,
             'generated_at' => null,
-        ]);
+        ];
+
+        try {
+            $record = KnowledgeExplanation::updateOrCreate([
+                'knowledge_id' => $knowledgeId,
+            ], $recordPayload);
+        } catch (QueryException $e) {
+            if (! $this->isDuplicatePrimaryKeyError($e)) {
+                throw $e;
+            }
+
+            // 兼容线上历史表主键异常(id 非正常自增):
+            // 1) 若 knowledge_id 已存在则直接更新;
+            // 2) 否则手动分配一个递增 id 再插入,避免任务失败。
+            $record = $this->persistWithManualIdFallback($knowledgeId, $recordPayload);
+        }
 
         return [
             'knowledge_id' => $knowledgeId,
@@ -232,6 +247,45 @@ class KnowledgeExplanationService
         return array_keys($codes);
     }
 
+    private function isDuplicatePrimaryKeyError(QueryException $e): bool
+    {
+        $message = (string) $e->getMessage();
+
+        return str_contains($message, 'Integrity constraint violation: 1062')
+            && str_contains($message, 'knowledge_explanations.PRIMARY');
+    }
+
+    private function persistWithManualIdFallback(string $knowledgeId, array $recordPayload): KnowledgeExplanation
+    {
+        $existing = KnowledgeExplanation::query()
+            ->where('knowledge_id', $knowledgeId)
+            ->first();
+        if ($existing) {
+            $existing->fill($recordPayload);
+            $existing->save();
+
+            return $existing;
+        }
+
+        return DB::transaction(function () use ($knowledgeId, $recordPayload): KnowledgeExplanation {
+            $table = (new KnowledgeExplanation())->getTable();
+            $maxId = (int) DB::table($table)->lockForUpdate()->max('id');
+            $nextId = $maxId + 1;
+            $now = now();
+
+            DB::table($table)->insert(array_merge($recordPayload, [
+                'id' => $nextId,
+                'knowledge_id' => $knowledgeId,
+                'created_at' => $now,
+                'updated_at' => $now,
+            ]));
+
+            return KnowledgeExplanation::query()
+                ->where('knowledge_id', $knowledgeId)
+                ->firstOrFail();
+        });
+    }
+
     private function loadStudentQuestionHistory(string $studentId): array
     {
         $done = PaperQuestion::query()

+ 18 - 0
app/Services/TaskManager.php

@@ -162,6 +162,18 @@ class TaskManager
 
         try {
             $payload = $this->buildCallbackPayload($task);
+            Log::info('TaskManager: 回调请求负载', [
+                'task_id' => $taskId,
+                'callback_url' => $task['callback_url'],
+                'callback_type' => $payload['callback_type'] ?? null,
+                'status' => $payload['status'] ?? null,
+                'paper_id' => $payload['paper_id'] ?? null,
+                'knowledge_id' => $payload['knowledge_id'] ?? null,
+                'pdf_url' => $payload['pdf_url'] ?? null,
+                'grading_pdf_url' => $payload['grading_pdf_url'] ?? null,
+                'pdfs' => $payload['pdfs'] ?? null,
+                'payload_keys' => array_keys($payload),
+            ]);
 
             $response = Http::timeout(30)->post($task['callback_url'], $payload);
 
@@ -295,6 +307,12 @@ class TaskManager
             // 兼容历史调用方(新调用方统一读取 paper_id)
             $basePayload['knowledge_id'] = $task['data']['knowledge_id'] ?? ($task['knowledge_id'] ?? null);
             $basePayload['pdfs'] = $task['pdfs'] ?? null;
+            // 兼容旧回调消费方:同时提供顶层 URL 字段,避免只读 pdf_url 导致“回调成功但前端无链接”
+            $basePayload['pdf_url'] = $task['pdfs']['all_pdf']
+                ?? $task['pdfs']['exam_paper_pdf']
+                ?? ($task['pdf_url'] ?? null);
+            $basePayload['grading_pdf_url'] = $task['pdfs']['grading_pdf']
+                ?? ($task['grading_pdf_url'] ?? null);
             $basePayload['exam_content'] = $task['exam_content'] ?? null;
             $basePayload['stats'] = $task['stats'] ?? null;
         } elseif ($task['type'] === self::TASK_TYPE_ANALYSIS) {