all(), [ 'paperId' => 'required|string|max:255', 'questions' => 'required|array|min:1', 'questions.*.question_bank_id' => 'required|integer', 'questions.*.student_answer' => 'nullable|string', 'questions.*.is_correct' => 'required|array|min:1', 'questions.*.is_correct.*' => 'integer|in:0,1', 'questions.*.teacher_comment' => 'nullable|string', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'error' => '参数验证失败', 'details' => $validator->errors() ], 422); } $paperId = $request->input('paperId'); $questionsData = $request->input('questions'); Log::info('开始处理试卷提交分析', [ 'paper_id' => $paperId, 'questions_count' => count($questionsData) ]); // 2. 通过 paperId 查询试卷获取 student_id $paper = Paper::where('paper_id', $paperId)->first(); if (!$paper) { return response()->json([ 'success' => false, 'error' => '试卷不存在', 'paper_id' => $paperId ], 404); } $studentId = $paper->student_id; if (!$studentId) { return response()->json([ 'success' => false, 'error' => '试卷未关联学生', 'paper_id' => $paperId ], 400); } // 3. 获取题目详情并转换数据格式 $transformedQuestions = $this->transformQuestionsData($questionsData, $paperId); // 4. 调用学情分析服务(可能失败,不影响错题本写入) $analysisResult = []; $analysisError = null; try { $examData = [ 'exam_id' => $paperId, 'student_id' => $studentId, 'questions' => $transformedQuestions['questions'], ]; $analysisResult = $this->analysisService->analyzeExamAnswers($examData); } catch (\Exception $e) { $analysisError = $e->getMessage(); Log::warning('学情分析失败,继续处理错题本', [ 'paper_id' => $paperId, 'error' => $analysisError ]); } // 5. 将错题写入错题本 $mistakesAdded = $this->addMistakesToBook( $studentId, $paperId, $questionsData, $transformedQuestions['question_details'] ); // 6. 更新试卷状态为已完成 $this->updatePaperStatus($paperId, $questionsData); Log::info('试卷提交分析完成', [ 'paper_id' => $paperId, 'student_id' => $studentId, 'questions_analyzed' => count($transformedQuestions['questions']), 'mistakes_added' => $mistakesAdded ]); return response()->json([ 'success' => true, 'data' => [ 'paper_id' => $paperId, 'student_id' => $studentId, 'analysis_summary' => $analysisResult['overall_summary'] ?? null, 'knowledge_point_analysis' => $analysisResult['knowledge_point_analysis'] ?? [], 'smart_quiz_recommendation' => $analysisResult['smart_quiz_recommendation'] ?? null, 'mastery_vector' => $analysisResult['mastery_vector'] ?? [], 'mistakes_added' => $mistakesAdded, 'total_questions' => count($questionsData), 'correct_count' => $this->countCorrectQuestions($questionsData), 'incorrect_count' => $mistakesAdded, 'analysis_error' => $analysisError, // 如果学情分析失败,返回错误信息 ], 'message' => "分析完成,新增 {$mistakesAdded} 条错题记录" . ($analysisError ? "(学情分析暂不可用)" : "") ]); } catch (\Exception $e) { Log::error('试卷提交分析失败', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'request_data' => $request->all() ]); return response()->json([ 'success' => false, 'error' => '分析失败:' . $e->getMessage() ], 500); } } /** * 转换前端数据格式为分析服务所需格式 */ private function transformQuestionsData(array $questionsData, string $paperId): array { $transformedQuestions = []; $questionDetails = []; $questionBankIds = array_column($questionsData, 'question_bank_id'); // 批量获取题目详情 $questionBankData = $this->fetchQuestionBankData($questionBankIds); // 获取试卷中题目的分数信息 $paperQuestions = PaperQuestion::where('paper_id', $paperId) ->whereIn('question_bank_id', $questionBankIds) ->get() ->keyBy('question_bank_id'); foreach ($questionsData as $index => $questionData) { $questionBankId = $questionData['question_bank_id']; $isCorrectArray = $questionData['is_correct']; $studentAnswer = $questionData['student_answer'] ?? null; $teacherComment = $questionData['teacher_comment'] ?? null; // 获取题目详情 $qbData = $questionBankData[$questionBankId] ?? null; $paperQuestion = $paperQuestions->get($questionBankId); // 确定题目分数 $maxScore = $paperQuestion->score ?? ($qbData['score'] ?? 5.0); $kpCode = $qbData['kp_code'] ?? ($paperQuestion->knowledge_point ?? 'K-GENERAL'); $kpName = $qbData['kp_name'] ?? $kpCode; $questionType = $qbData['question_type'] ?? ($paperQuestion->question_type ?? 'unknown'); // 保存详情供后续使用 $questionDetails[$questionBankId] = [ 'question_bank_id' => $questionBankId, 'stem' => $qbData['stem'] ?? ($paperQuestion->question_text ?? ''), 'answer' => $qbData['answer'] ?? ($paperQuestion->correct_answer ?? ''), 'solution' => $qbData['solution'] ?? ($paperQuestion->solution ?? ''), 'kp_code' => $kpCode, 'kp_name' => $kpName, 'question_type' => $questionType, 'max_score' => $maxScore, 'skills' => $qbData['skills'] ?? [], ]; // 转换 is_correct 数组为 steps 格式 $steps = []; $totalSteps = count($isCorrectArray); $scorePerStep = $maxScore / $totalSteps; $scoreObtained = 0; foreach ($isCorrectArray as $stepIndex => $isCorrect) { $stepCorrect = (bool) $isCorrect; if ($stepCorrect) { $scoreObtained += $scorePerStep; } $steps[] = [ 'step_index' => $stepIndex + 1, 'is_correct' => $stepCorrect, 'kp_id' => $kpCode, 'score' => $scorePerStep, 'weight' => 1.0, ]; } $transformedQuestions[] = [ 'question_id' => (string) $questionBankId, 'score' => $maxScore, 'score_obtained' => round($scoreObtained, 2), 'steps' => $totalSteps > 1 ? $steps : [], // 单步骤不传steps ]; } return [ 'questions' => $transformedQuestions, 'question_details' => $questionDetails, ]; } /** * 批量获取题库题目详情 */ private function fetchQuestionBankData(array $questionBankIds): array { try { $response = $this->questionBankService->getQuestionsByIds($questionBankIds); $questions = $response['data'] ?? $response; // 转换为以 id 为 key 的数组 $result = []; foreach ($questions as $question) { $id = $question['id'] ?? $question['question_id'] ?? null; if ($id) { $result[$id] = $question; } } return $result; } catch (\Exception $e) { Log::warning('获取题库详情失败,使用空数据', [ 'error' => $e->getMessage(), 'question_bank_ids' => $questionBankIds ]); return []; } } /** * 将错题写入错题本 */ private function addMistakesToBook( string $studentId, string $paperId, array $questionsData, array $questionDetails ): int { $mistakesAdded = 0; foreach ($questionsData as $questionData) { $isCorrectArray = $questionData['is_correct']; // 判断是否有错误(数组中存在0) $hasError = in_array(0, $isCorrectArray, true); if (!$hasError) { continue; } $questionBankId = $questionData['question_bank_id']; $detail = $questionDetails[$questionBankId] ?? []; try { $payload = [ 'student_id' => $studentId, 'question_id' => $questionBankId, 'paper_id' => $paperId, 'my_answer' => $questionData['student_answer'] ?? '', 'correct_answer' => $detail['answer'] ?? '', 'question_text' => $detail['stem'] ?? '', 'knowledge_point' => $detail['kp_name'] ?? '', 'explanation' => $detail['solution'] ?? '', 'kp_ids' => [$detail['kp_code'] ?? 'K-GENERAL'], 'source' => "paper:{$paperId}", 'happened_at' => now()->toISOString(), ]; $result = $this->mistakeBookService->createMistake($payload); // 如果不是重复记录,则计数 if (!($result['duplicate'] ?? false)) { $mistakesAdded++; } Log::debug('错题写入成功', [ 'student_id' => $studentId, 'question_bank_id' => $questionBankId, 'duplicate' => $result['duplicate'] ?? false ]); } catch (\Exception $e) { Log::error('写入错题本失败', [ 'student_id' => $studentId, 'question_bank_id' => $questionBankId, 'error' => $e->getMessage() ]); } } return $mistakesAdded; } /** * 更新试卷状态和题目作答信息 */ private function updatePaperStatus(string $paperId, array $questionsData): void { try { // 更新试卷状态为已完成 Paper::where('paper_id', $paperId)->update([ 'status' => 'completed', 'completed_at' => now(), ]); // 更新每道题目的作答信息 foreach ($questionsData as $questionData) { $questionBankId = $questionData['question_bank_id']; $isCorrectArray = $questionData['is_correct']; // 计算得分比例 $correctCount = array_sum($isCorrectArray); $totalSteps = count($isCorrectArray); $scoreRatio = $totalSteps > 0 ? $correctCount / $totalSteps : 0; // 判断是否全对 $isFullyCorrect = !in_array(0, $isCorrectArray, true); PaperQuestion::where('paper_id', $paperId) ->where('question_bank_id', $questionBankId) ->update([ 'student_answer' => $questionData['student_answer'] ?? null, 'is_correct' => $isFullyCorrect, 'score_ratio' => $scoreRatio, 'score_obtained' => DB::raw("score * {$scoreRatio}"), 'teacher_comment' => $questionData['teacher_comment'] ?? null, 'graded_at' => now(), ]); } Log::info('试卷状态更新完成', ['paper_id' => $paperId]); } catch (\Exception $e) { Log::error('更新试卷状态失败', [ 'paper_id' => $paperId, 'error' => $e->getMessage() ]); } } /** * 统计正确题目数量 */ private function countCorrectQuestions(array $questionsData): int { $correctCount = 0; foreach ($questionsData as $questionData) { $isCorrectArray = $questionData['is_correct']; // 全对才算正确 if (!in_array(0, $isCorrectArray, true)) { $correctCount++; } } return $correctCount; } /** * 获取试卷分析结果 * * GET /api/paper-submit-analysis/{paperId} */ public function getResult(string $paperId): JsonResponse { try { $paper = Paper::where('paper_id', $paperId)->first(); if (!$paper) { return response()->json([ 'success' => false, 'error' => '试卷不存在' ], 404); } // 从数据库获取分析结果 $result = DB::connection('mysql') ->table('exam_analysis_results') ->where('exam_id', $paperId) ->where('student_id', $paper->student_id) ->orderBy('created_at', 'desc') ->first(); if (!$result) { return response()->json([ 'success' => false, 'error' => '未找到分析结果,请先提交试卷进行分析' ], 404); } return response()->json([ 'success' => true, 'data' => json_decode($result->analysis_data, true) ]); } catch (\Exception $e) { Log::error('获取试卷分析结果失败', [ 'paper_id' => $paperId, 'error' => $e->getMessage() ]); return response()->json([ 'success' => false, 'error' => '获取分析结果失败:' . $e->getMessage() ], 500); } } }