|
|
@@ -5,9 +5,11 @@ namespace App\Services;
|
|
|
use App\DTO\ExamAnalysisDataDto;
|
|
|
use App\DTO\ReportPayloadDto;
|
|
|
use App\Jobs\ProcessAnalysisReportTaskJob;
|
|
|
+use App\Models\OCRRecord;
|
|
|
use App\Models\Paper;
|
|
|
use App\Models\PaperQuestion;
|
|
|
use App\Models\Student;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
|
/**
|
|
|
@@ -25,12 +27,63 @@ class ExamAnalysisService
|
|
|
private readonly ExamAnswerAnalysisService $examAnswerAnalysisService
|
|
|
) {}
|
|
|
|
|
|
+ /**
|
|
|
+ * 入队前校验:试卷/学生/分析数据等,避免无效任务进入 pdf 队列占用 worker。
|
|
|
+ *
|
|
|
+ * @throws \InvalidArgumentException
|
|
|
+ */
|
|
|
+ public function validateAnalysisReportEnqueue(string $paperId, string $studentId, ?string $recordId = null): void
|
|
|
+ {
|
|
|
+ $studentIdNorm = (string) $studentId;
|
|
|
+
|
|
|
+ $paper = Paper::query()->select(['paper_id', 'student_id'])->find($paperId);
|
|
|
+ if (! $paper) {
|
|
|
+ throw new \InvalidArgumentException('试卷不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($paper->student_id !== null && $paper->student_id !== ''
|
|
|
+ && (string) $paper->student_id !== $studentIdNorm) {
|
|
|
+ throw new \InvalidArgumentException('学生与试卷不匹配');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (! Student::query()->where('student_id', $studentId)->exists()) {
|
|
|
+ throw new \InvalidArgumentException('学生不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($recordId !== null && $recordId !== '') {
|
|
|
+ $ocr = OCRRecord::query()->find($recordId);
|
|
|
+ if (! $ocr) {
|
|
|
+ throw new \InvalidArgumentException('OCR 记录不存在');
|
|
|
+ }
|
|
|
+ if ((string) $ocr->student_id !== $studentIdNorm) {
|
|
|
+ throw new \InvalidArgumentException('OCR 记录与学生不匹配');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $row = DB::connection('mysql')->table('exam_analysis_results')
|
|
|
+ ->where('paper_id', $paperId)
|
|
|
+ ->where('student_id', $studentId)
|
|
|
+ ->orderByDesc('created_at')
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (! $row || $row->analysis_data === null || $row->analysis_data === '') {
|
|
|
+ throw new \InvalidArgumentException('尚未完成答题分析或分析数据为空,请先完成阅卷分析后再生成报告');
|
|
|
+ }
|
|
|
+
|
|
|
+ $decoded = json_decode((string) $row->analysis_data, true);
|
|
|
+ if (! is_array($decoded) || $decoded === []) {
|
|
|
+ throw new \InvalidArgumentException('分析数据无效,无法生成 PDF');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 生成学情报告
|
|
|
* 异步模式,立即返回任务ID
|
|
|
*/
|
|
|
public function generateReport(string $paperId, string $studentId, ?string $recordId = null): string
|
|
|
{
|
|
|
+ $this->validateAnalysisReportEnqueue($paperId, $studentId, $recordId);
|
|
|
+
|
|
|
// 创建异步任务
|
|
|
$taskId = $this->taskManager->createTask(
|
|
|
TaskManager::TASK_TYPE_ANALYSIS,
|