Files
my-springboot-project/ExamController.java
2026-06-03 12:43:48 +08:00

363 lines
16 KiB
Java
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<ExamRecordResponse> 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<ExamRecordResponse> 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<ExamPaperGenerateResponse> 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<Map<String, Object>> 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<String, Object> 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<ExamRecordResponse> 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<Object> 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<ExamSaveRequest.AnswerItem> 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<UserAnswerQueryResponse> 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<Object> 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<ExamRecordResponse> 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());
}
}
}