package top.tqx.demo_1.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import top.tqx.demo_1.common.Result; import top.tqx.demo_1.dto.*; import top.tqx.demo_1.entity.Question; import top.tqx.demo_1.enums.AnswerType; import top.tqx.demo_1.service.AuthService; import top.tqx.demo_1.service.ExamService; import top.tqx.demo_1.service.ExamRecordService; import top.tqx.demo_1.service.UserAnswerService; import top.tqx.demo_1.service.UserService; import top.tqx.demo_1.vo.UserVO; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @Tag(name = "考试管理", description = "题目生成与答案批改接口") @RestController @RequestMapping("/exam") @Slf4j public class ExamController extends BaseController { @Autowired private ExamService examService; @Autowired private AuthService authService; @Autowired private UserService userService; @Autowired private UserAnswerService userAnswerService; @Autowired private top.tqx.demo_1.service.ExamRecordService examRecordService; @Operation(summary = "开始考试(获取题目)", description = "通过试卷ID开始模拟考试,同时返回试卷题目内容") @PostMapping("/paper/{paperId}/start") public Result startExamByPaperId( @PathVariable String paperId, HttpServletRequest httpRequest) { try { Long userId = getCurrentUserId(httpRequest); ExamRecordResponse response = examRecordService.startExamByPaperId(paperId, userId, AnswerType.PRACTICE.getCode()); return Result.success(response); } catch (Exception e) { log.error("开始考试失败: {}", e.getMessage()); return Result.error(500, e.getMessage()); } } @Operation(summary = "获取考试结果", description = "获取模拟考试的完整结果,包括题目、答案、分数等信息") @GetMapping("/record/{recordId}/result") public Result getExamResult( @PathVariable String recordId, HttpServletRequest httpRequest) { try { Long userId = getCurrentUserId(httpRequest); ExamRecordResponse response = examRecordService.getRecordById(recordId); if (response == null) { return Result.error(404, "记录不存在"); } if (!response.getUserId().equals(userId)) { return Result.error(403, "无权查看此记录"); } return Result.success(response); } catch (Exception e) { log.error("获取考试结果失败: {}", e.getMessage()); return Result.error(500, e.getMessage()); } } @Operation(summary = "生成试卷", description = "根据权限范围从题库中动态生成试卷") @PostMapping("/paper/generate") public Result generatePaper( @RequestBody ExamPaperGenerateRequest request, HttpServletRequest httpRequest) { Long userId = getCurrentUserId(httpRequest); if (userId == null) { return Result.error(401, "用户未登录"); } UserVO userVO = userService.getUserById(userId); if (userVO == null) { return Result.error(401, "用户不存在"); } if (request.getIncludePersonal() == null) { request.setIncludePersonal(false); } log.info("生成试卷请求: userId={}, deptId={}, userType={}, includePersonal={}, singleChoiceCount={}, multipleChoiceCount={}, trueFalseCount={}, fillBlankCount={}, subjectiveCount={}, difficulty={}", userId, userVO.getDeptId(), userVO.getUserType(), request.getIncludePersonal(), request.getSingleChoiceCount(), request.getMultipleChoiceCount(), request.getTrueFalseCount(), request.getFillBlankCount(), request.getSubjectiveCount(), request.getDifficulty()); try { ExamPaperGenerateResponse response = examService.generatePaper(request, userId, userVO.getDeptId(), userVO.getUserType()); return Result.success("试卷生成成功", response); } catch (Exception e) { log.error("生成试卷异常: {}", e.getMessage(), e); return Result.error(500, "生成试卷异常: " + e.getMessage()); } } @Operation(summary = "生成题目", description = "根据知识库内容生成考试题目,支持单选题、多选题、判断题、填空题、简答题") @PostMapping("/generate") public Result> generateQuestions( @RequestBody ExamGenerateRequest request, HttpServletRequest httpRequest) { Long userId = getCurrentUserId(httpRequest); if (userId == null) { return Result.error(401, "用户未登录"); } if (request.getQuestionTypes() == null) { return Result.error(400, "question_types 不能为空,请指定题型及数量"); } boolean hasCollection = (request.getCollection() != null) || (request.getCollectionName() != null && !request.getCollectionName().isEmpty()); if (!hasCollection) { return Result.error(400, "collection 或 collection_name 不能为空,请指定向量库"); } if (request.getFilePath() == null || request.getFilePath().isEmpty()) { return Result.error(400, "file_path 不能为空,请指定文件路径"); } if (request.getRequestId() == null || request.getRequestId().isEmpty()) { request.setRequestId(UUID.randomUUID().toString()); } log.info("生成题目请求: userId={}, requestId={}, questionTypes={}, difficulty={}, collection={}, collectionName={}, filePath={}", userId, request.getRequestId(), request.getQuestionTypes(), request.getDifficulty(), request.getCollection(), request.getCollectionName(), request.getFilePath()); examService.generateQuestionsAsync(request); Map result = new HashMap<>(); result.put("requestId", request.getRequestId()); result.put("message", "收到请求,正在生成题目,请稍后查看文件状态"); return Result.success("收到请求", result); } @Operation(summary = "提交考试并批改(正式考试专用)", description = "通过考试记录ID提交正式考试答案并批改") @PostMapping("/record/{recordId}/grade") public Result gradeExamRecord( @PathVariable String recordId, @RequestBody ExamSaveRequest request, HttpServletRequest httpRequest) { Long userId = getCurrentUserId(httpRequest); if (userId == null) { return Result.error(401, "用户未登录"); } log.info("提交正式考试: userId={}, recordId={}", userId, recordId); try { ExamRecordResponse response = examService.gradeAnswersForRecord(recordId, request, userId, "formal_exam"); return Result.success("提交成功", response); } catch (Exception e) { log.error("提交正式考试失败: {}", e.getMessage(), e); return Result.error(500, "提交失败: " + e.getMessage()); } } @Operation(summary = "批改答案(日常练习/模拟考/正式考试通用)", description = "对学生提交的答案进行批改,支持客观题本地批改和主观题LLM评分") @PostMapping("/grade") public Result gradeAnswers( @RequestBody ExamGradeRequest request, HttpServletRequest httpRequest) { Long userId = getCurrentUserId(httpRequest); if (userId == null) { return Result.error(401, "用户未登录"); } if (request.getAnswers() == null || request.getAnswers().isEmpty()) { return Result.error(400, "答案列表不能为空"); } // 检查是否是通过 recordId 提交的正式考试 if (request.getRecordId() != null && !request.getRecordId().isEmpty()) { // 需要转换 ExamGradeRequest 为 ExamSaveRequest ExamSaveRequest saveRequest = new ExamSaveRequest(); List saveItems = request.getAnswers().stream().map(gradeItem -> { ExamSaveRequest.AnswerItem saveItem = new ExamSaveRequest.AnswerItem(); saveItem.setQuestionId(gradeItem.getQuestionId()); saveItem.setQuestionType(gradeItem.getQuestionType()); saveItem.setAnswer(gradeItem.getStudentAnswer()); return saveItem; }).collect(java.util.stream.Collectors.toList()); saveRequest.setAnswers(saveItems); log.info("通过recordId提交考试: userId={}, recordId={}", userId, request.getRecordId()); try { ExamRecordResponse response = examService.gradeAnswersForRecord( request.getRecordId(), saveRequest, userId, request.getAnswerType() != null ? request.getAnswerType() : "formal_exam"); return Result.success("批阅成功", response); } catch (Exception e) { log.error("批改失败: {}", e.getMessage(), e); return Result.error(500, "批改失败: " + e.getMessage()); } } // 日常练习/模拟考模式 for (int i = 0; i < request.getAnswers().size(); i++) { ExamGradeRequest.AnswerItem answer = request.getAnswers().get(i); if (answer.getQuestionId() == null || answer.getQuestionId().isEmpty()) { return Result.error(400, "第" + (i + 1) + "个答案的question_id不能为空"); } if (answer.getQuestionType() == null || answer.getQuestionType().isEmpty()) { return Result.error(400, "第" + (i + 1) + "个答案的question_type不能为空"); } if (answer.getQuestionContent() == null) { return Result.error(400, "第" + (i + 1) + "个答案的question_content不能为空"); } if (answer.getMaxScore() == null) { return Result.error(400, "第" + (i + 1) + "个答案的max_score不能为空"); } } if (request.getRequestId() == null || request.getRequestId().isEmpty()) { request.setRequestId(UUID.randomUUID().toString()); } if (request.getPaperId() == null || request.getPaperId().isEmpty()) { String generatedPaperId = UUID.randomUUID().toString(); request.setPaperId(generatedPaperId); log.info("自动生成试卷ID: paperId={}", generatedPaperId); } if (request.getSessionId() == null || request.getSessionId().isEmpty()) { request.setSessionId("grade_" + UUID.randomUUID().toString().substring(0, 8)); } log.info("批改答案请求: userId={}, requestId={}, paperId={}, sessionId={}, type={}, answers count={}", userId, request.getRequestId(), request.getPaperId(), request.getSessionId(), request.getAnswerType(), request.getAnswers().size()); try { ExamGradeResponse response = examService.gradeAnswers(request, userId, request.getPaperId(), request.getSessionId(), request.getAnswerType()); if (Boolean.TRUE.equals(response.getSuccess())) { return Result.success("批阅成功", response); } else { String errorMessage = response.getError() != null ? response.getError() : "答案批改失败"; return Result.error(500, errorMessage); } } catch (IOException e) { log.error("批改答案失败: {}", e.getMessage(), e); return Result.error(500, "批改答案失败: " + e.getMessage()); } catch (Exception e) { log.error("批改答案异常: {}", e.getMessage(), e); return Result.error(500, "批改答案异常: " + e.getMessage()); } } @Operation(summary = "查询用户答题记录", description = "根据试卷ID或会话ID查询用户答题记录,支持按试卷/练习维度分组展示") @PostMapping("/answers/query") public Result queryUserAnswers( @RequestBody UserAnswerQueryRequest request, HttpServletRequest httpRequest) { Long userId = getCurrentUserId(httpRequest); if (userId == null) { return Result.error(401, "用户未登录"); } boolean hasPaperId = request.getPaperId() != null && !request.getPaperId().isEmpty(); boolean hasSessionId = request.getSessionId() != null && !request.getSessionId().isEmpty(); if (!hasPaperId && !hasSessionId) { return Result.error(400, "paper_id 或 session_id 不能为空"); } if (hasPaperId && hasSessionId) { return Result.error(400, "paper_id 和 session_id 不能同时提供"); } request.setUserId(userId); log.info("查询用户答题记录: userId={}, paperId={}, sessionId={}", userId, request.getPaperId(), request.getSessionId()); try { UserAnswerQueryResponse response = userAnswerService.queryUserAnswers(request); if (response.getTotalCount() == 0) { return Result.success("未找到答题记录", response); } return Result.success("查询成功", response); } catch (Exception e) { log.error("查询用户答题记录异常: {}", e.getMessage(), e); return Result.error(500, "查询用户答题记录异常: " + e.getMessage()); } } @Operation(summary = "获取考试记录列表", description = "根据类型获取用户的考试/练习记录,支持日常练习(practice)、模拟考(mock_exam)、正式考(formal_exam)") @GetMapping("/records") public Result getExamRecords( @Parameter(description = "记录类型:practice(日常练习), mock_exam(模拟考), formal_exam(正式考)", example = "practice") @RequestParam(defaultValue = "practice") String type, HttpServletRequest httpRequest) { Long userId = getCurrentUserId(httpRequest); if (userId == null) { return Result.error(401, "用户未登录"); } log.info("查询考试记录: userId={}, type={}", userId, type); try { // 根据类型选择不同的查询逻辑 if ("practice".equals(type)) { // 日常练习:从 user_answer 表查询,按 session_id 分组 UserAnswerQueryRequest queryRequest = new UserAnswerQueryRequest(); queryRequest.setUserId(userId); UserAnswerQueryResponse response = userAnswerService.queryUserAnswers(queryRequest); return Result.success(response); } else if ("mock_exam".equals(type) || "formal_exam".equals(type)) { // 模拟考/正式考:从 exam_record 表查询 List records = examRecordService.getUserPapers(userId, type); return Result.success(records); } else { return Result.error(400, "无效的类型参数,支持的值: practice, mock_exam, formal_exam"); } } catch (Exception e) { log.error("查询考试记录失败: {}", e.getMessage(), e); return Result.error(500, "查询考试记录失败: " + e.getMessage()); } } }