初始化项目代码

This commit is contained in:
2026-06-03 12:43:48 +08:00
commit 5b12356567
303 changed files with 40729 additions and 0 deletions

363
ExamController.java Normal file
View File

@@ -0,0 +1,363 @@
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());
}
}
}