diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/controller/ExamBizController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/controller/ExamBizController.java index 4b0c40b2..6835cf5a 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/controller/ExamBizController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/controller/ExamBizController.java @@ -1,5 +1,7 @@ package org.jeecg.modules.biz.controller; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; @@ -18,11 +20,16 @@ import org.jeecg.modules.gen.paper.entity.Paper; import org.jeecg.modules.gen.paper.service.IPaperService; import org.jeecg.modules.gen.paperquestion.entity.PaperQuestion; import org.jeecg.modules.gen.paperquestion.service.IPaperQuestionService; +import org.jeecg.modules.gen.question.controller.QuestionController; import org.jeecg.modules.gen.question.entity.Question; +import org.jeecg.modules.gen.question.entity.QuestionRequest; import org.jeecg.modules.gen.question.service.IQuestionService; +import org.jeecg.modules.gen.questionoption.entity.QuestionOption; +import org.jeecg.modules.gen.questionoption.service.IQuestionOptionService; import org.jeecg.modules.gen.questionrepo.entity.QuestionRepo; import org.jeecg.modules.gen.questionrepo.service.IQuestionRepoService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; @@ -48,10 +55,13 @@ public class ExamBizController { private IQuestionService questionService; @Autowired private IQuestionRepoService questionRepoService; + @Autowired + private IQuestionOptionService questionOptionService; //获取考试试题 @RequestMapping("/getExamQuestions/{examId}") @Operation(summary = "获取考试试题") + @Transactional public Result getExamQuestions(@PathVariable String examId, @RequestParam String studentId) { Exam exam = examService.getById(examId); if(exam.getPaperId().isEmpty()){ @@ -67,9 +77,8 @@ public class ExamBizController { new LambdaQueryWrapper(). eq(QuestionRepo::getRepoId, paper.getRepoId()) ); - questionIds = list.stream() - .map(QuestionRepo::getQuestionId) - .collect(Collectors.toList()); + //筛选试卷 + questionIds = random(list, paper.getRules()); }else { //固定组卷 // 获取试卷中的试题关联信息 @@ -168,6 +177,7 @@ public class ExamBizController { @PostMapping("/submitExam") @Operation(summary = "提交考试") + @Transactional public Result submitExam(@RequestBody ExamRecord examRecord, HttpServletRequest req) { examRecord.setIpAddress(getClientIp(req)); @@ -186,6 +196,9 @@ public class ExamBizController { if (examRecord.getDeviceInfo() != null) { updateWrapper.set(ExamRecord::getDeviceInfo, examRecord.getDeviceInfo()); } + // 阅卷 + List gradedAnswers = gradeExam(examRecord.getExamId(), examRecord.getUserId()); + examAnswerService.updateBatchById(gradedAnswers); // 更新考试状态,提交时间 updateWrapper. set(ExamRecord::getStatus,1). @@ -194,6 +207,67 @@ public class ExamBizController { return examRecordService.update(updateWrapper) ? Result.OK() : Result.error("提交考试失败"); } + @GetMapping("/queryExamProgress") + @Operation(summary = "查询考试进度") + public Result queryExamProgress(@RequestParam String examId, @RequestParam String userId) { + Exam byId = examService.getById(examId); + if(byId == null){ + return Result.error("考试不存在"); + } + //判断考试结束时间 + if(byId.getEndTime().before(new Date())){ + return Result.error("考试已结束"); + } + ExamRecord one = examRecordService.getOne( + new LambdaQueryWrapper() + .eq(ExamRecord::getExamId, examId) + .eq(ExamRecord::getUserId, userId) + ); + if(one == null){ + return Result.error("用户暂未考试,可获取考试题目"); + } + return Result.OK(examAnswerService. + list(new LambdaQueryWrapper(). + eq(ExamAnswer::getExamId, examId). + eq(ExamAnswer::getUserId, userId)) + ); + } + + //根据考试规则随机组卷 + public List random(List list, String rules) { + JSONObject ruleJson = JSON.parseObject(rules); + + // 根据规则筛选和随机抽取题目 + Map> typeQuestions = new HashMap<>(); + + // 先按题目类型分组 + list.forEach(qr -> { + Question question = questionService.getById(qr.getQuestionId()); + if (!typeQuestions.containsKey(question.getType())) { + typeQuestions.put(question.getType(), new ArrayList<>()); + } + typeQuestions.get(question.getType()).add(question.getId()); + }); + + // 根据规则随机抽取题目 + List questionIds = new ArrayList<>(); + for (Integer type : typeQuestions.keySet()) { + int count = ruleJson.getInteger("type"+type + "_count"); // 例如:single_choice_count + List typeQuestionIds = typeQuestions.get(type); + + // 随机抽取指定数量的题目 + if (typeQuestionIds.size() <= count) { + questionIds.addAll(typeQuestionIds); + } else { + // 打乱顺序后取前count个 + Collections.shuffle(typeQuestionIds); + questionIds.addAll(typeQuestionIds.subList(0, count)); + } + } + return questionIds; + } + + //获取ip信息 public String getClientIp(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); @@ -219,6 +293,91 @@ public class ExamBizController { return ip; } + /** + * 批量阅卷 + * @param examId 考试ID + * @param userId 用户ID + * @return 阅卷后的答题列表 + */ + private List gradeExam(String examId, String userId) { + // 获取学生的答题列表 + List examAnswerList = examAnswerService.list(new LambdaQueryWrapper() + .eq(ExamAnswer::getExamId, examId) + .eq(ExamAnswer::getUserId, userId) + ); + + // 提取所有题目ID + List questionIds = examAnswerList.stream() + .map(ExamAnswer::getQuestionId) + .collect(Collectors.toList()); + + // 查询题目 + List questions = questionService.list(new LambdaQueryWrapper() + .in(Question::getId, questionIds) + .lt(Question::getType, 3) + ); + + // 创建题目ID到题目的映射 + Map questionMap = questions.stream() + .collect(Collectors.toMap(Question::getId, question -> question)); + + // 获取 选择、多选、判断 题目ID列表 + List questionIdsFromQuestions = questions.stream() + .map(Question::getId) + .collect(Collectors.toList()); + + // 查询这些题目的正确选项 + List questionOptions = questionOptionService.list(new LambdaQueryWrapper() + .in(QuestionOption::getQuestionId, questionIdsFromQuestions) + .eq(QuestionOption::getIzCorrent, 1) + ); + + // 将选项转换为Map,结构为:题目ID -> 正确答案选项ID列表 + Map> correctAnswerMap = questionOptions.stream() + .collect(Collectors.groupingBy( + QuestionOption::getQuestionId, + Collectors.mapping(QuestionOption::getId, Collectors.toList()) + )); + + // 遍历学生的答案,进行评分 + for (ExamAnswer examAnswer : examAnswerList) { + String studentAnswer = examAnswer.getAnswer(); + Question question = questionMap.get(examAnswer.getQuestionId()); + List correctAnswers = correctAnswerMap.get(examAnswer.getQuestionId()); + + // 比较答案并设置分数 + if (studentAnswer != null && question != null && correctAnswers != null) { + // 将学生答案按逗号分割成列表 + List studentAnswers = Arrays.asList(studentAnswer.split(",")); + double score = 0.0; + + // 根据题目类型进行评分 + if (question.getType() == 1 || question.getType() == 2) { // 单选题或判断题 + if (studentAnswers.get(0).equals(correctAnswers.get(0))) { + score = question.getScore(); // 使用题目设定的分值 + } + } else if (question.getType() == 3) { // 多选题 + // 检查学生答案数量是否正确 + if (studentAnswers.size() == correctAnswers.size()) { + // 检查每个答案是否都正确 + boolean allCorrect = studentAnswers.stream() + .allMatch(correctAnswers::contains); + + if (allCorrect) { + score = question.getScore(); // 使用题目设定的分值 + } + } + } + examAnswer.setIzCorrect(score> 0.0 ? 1 : 0); + examAnswer.setScore(score); + } else { + examAnswer.setScore(0.0); // 答案为空或题目不存在 + } + } + return examAnswerList; + } + + //获取设备信息 public String getDeviceInfo(HttpServletRequest request) { // 获取User-Agent String userAgent = request.getHeader("User-Agent"); @@ -226,7 +385,6 @@ public class ExamBizController { String deviceInfo = parseDeviceInfo(userAgent); return deviceInfo; } - private String parseDeviceInfo(String userAgent) { if (userAgent == null) { return "Unknown"; diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/controller/RepoBizController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/controller/RepoBizController.java index aee92008..936d1c0b 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/controller/RepoBizController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/controller/RepoBizController.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.api.vo.Result; import org.jeecg.modules.biz.constant.EntityLinkConst; +import org.jeecg.modules.biz.dto.QuestionAnswerDTO; import org.jeecg.modules.biz.service.EntityLinkBizService; import org.jeecg.modules.gen.question.entity.Question; import org.jeecg.modules.gen.question.service.IQuestionService; @@ -41,7 +42,7 @@ public class RepoBizController { @Autowired private EntityLinkBizService entityLinkBizService; @Autowired - private IQuestionService questionService; + private IQuestionService questionService; @Autowired private IQuestionOptionService questionOptionService; @Autowired @@ -64,19 +65,33 @@ public class RepoBizController { return Result.OK(repo.getId()); } + @GetMapping("repoList") + @Operation(summary = "获取所有题库") + public Result> repoList() { + return Result.ok(repoService.list()); + } + @PostMapping(value = "/courseAdd") @Operation(summary = "课程新建题库") public Result courseAdd(@RequestBody Repo repo) { - return repoService.save(repo)?Result.OK("添加成功!"):Result.error("添加失败!"); + return repoService.save(repo) ? Result.OK("添加成功!") : Result.error("添加失败!"); } @GetMapping("/questionList/{repoId}") @Operation(summary = "查询题库下题目") public Result> questionList(@PathVariable String repoId) { - List list = questionRepoService.questionList(repoId); - List questions = questionService.listByIds(list); + // 获取题库中的题目ID列表 + List repoQuestionIds = questionRepoService.questionList(repoId); + if (repoQuestionIds.isEmpty()) { + return Result.error("该题库下没有题目或该题库不存在"); + } + // 根据ID列表查询题目 + List questions = questionService.listByIds(repoQuestionIds); + if(questions.isEmpty()){ + return Result.error("题目不存在"); + } //获取复选题id List type5Ids = questions.stream() .filter(question -> question.getType() == 5) @@ -121,13 +136,20 @@ public class RepoBizController { @GetMapping("/repoList/{questionId}") @Operation(summary = "查询题目详情") - public Result questionDetail(@PathVariable String questionId,@RequestParam Integer type) { - if (type == 0 || type == 1 || type == 2) { - List questionOptions = choiceDetail(questionId); - return Result.ok(questionOptions); - }else if(type == 3 || type == 4) { - return Result.ok(answerDetail(questionId)); - }else { + public Result questionDetail(@PathVariable String questionId) { + Question rootQuestion = questionService.getById(questionId); + if (rootQuestion == null) { + return Result.error("题目不存在"); + } + QuestionAnswerDTO questionAnswerDTO = new QuestionAnswerDTO(); + questionAnswerDTO.setQuestion(rootQuestion); + if (rootQuestion.getType() >= 0 && rootQuestion.getType() <= 2) { + questionAnswerDTO.setAnswer(choiceDetail(questionId)); + return Result.ok(questionAnswerDTO); + } else if (rootQuestion.getType() == 3 || rootQuestion.getType() == 4) { + questionAnswerDTO.setAnswer(answerDetail(questionId)); + return Result.ok(questionAnswerDTO); + } else { //查询复合题所包含的题目 List list = questionService.list( new LambdaQueryWrapper(). @@ -138,35 +160,31 @@ public class RepoBizController { .collect(Collectors.partitioningBy( q -> q.getType() > 2 )); - Map>> questionOptionMap = new HashMap<>(); //获取选择题,多选题,判断题答案 List question = groupedQuestions.get(false); - if(!question.isEmpty()){ - Map> questionListHashMap = new HashMap<>(); + if (!question.isEmpty()) { question.forEach(q -> { - ArrayList objects = new ArrayList<>(); - objects.add(q); - objects.add(choiceDetail(q.getId())); - questionListHashMap.put(q.getId(), objects); + QuestionAnswerDTO qad = new QuestionAnswerDTO(); + qad.setQuestion(q); + qad.setAnswer(choiceDetail(q.getId())); + questionAnswerDTO.getChildren().add(qad); }); - questionOptionMap.put("questionOption", questionListHashMap); } //获取填空题,简答题答案 List question1 = groupedQuestions.get(true); - if(!question1.isEmpty()){ - Map> questionAnswerMap = new HashMap<>(); + if (!question1.isEmpty()) { question1.forEach(q -> { - ArrayList objects = new ArrayList<>(); - objects.add(q); - objects.add(answerDetail(q.getId())); - questionAnswerMap.put(q.getId(), objects); + QuestionAnswerDTO qad = new QuestionAnswerDTO(); + qad.setQuestion(q); + qad.setAnswer(answerDetail(q.getId())); + questionAnswerDTO.getChildren().add(qad); }); - questionOptionMap.put("questionAnswer", questionAnswerMap); } - return Result.ok(questionOptionMap); + return Result.ok(questionAnswerDTO); } } + //查询选择题,多选题,判断题答案 public List choiceDetail(String questionId) { return questionOptionService.list( @@ -180,7 +198,8 @@ public class RepoBizController { public List answerDetail(String questionId) { return questionAnswerService.list( new LambdaQueryWrapper(). - eq(QuestionAnswer::getQuestionId, questionId) + eq(QuestionAnswer::getQuestionId, questionId). + orderByAsc(QuestionAnswer::getOrderNo) ); } diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/dto/QuestionAnswerDTO.java b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/dto/QuestionAnswerDTO.java new file mode 100644 index 00000000..37e2dbaf --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/biz/dto/QuestionAnswerDTO.java @@ -0,0 +1,17 @@ +package org.jeecg.modules.biz.dto; + +import lombok.Data; +import org.jeecg.modules.gen.question.entity.Question; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class QuestionAnswerDTO { + //题目内容 + private Question question; + //答案 + private List answer; + //子题目列表 + private List children = new ArrayList<>(); +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/exam/controller/ExamController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/exam/controller/ExamController.java index ef413418..854cf419 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/exam/controller/ExamController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/exam/controller/ExamController.java @@ -89,7 +89,7 @@ public class ExamController extends JeecgController { public Result add(@RequestBody Exam exam) { examService.save(exam); - return Result.OK("添加成功!"); + return Result.OK(exam.getId()); } /** diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/paper/controller/PaperController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/paper/controller/PaperController.java index 593522a3..fe4067b9 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/paper/controller/PaperController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/paper/controller/PaperController.java @@ -102,7 +102,7 @@ public class PaperController extends JeecgController { public Result add(@RequestBody Paper paper) { paperService.save(paper); - return Result.OK("添加成功!"); + return Result.OK(paper.getId()); } /** diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/paperquestion/controller/PaperQuestionController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/paperquestion/controller/PaperQuestionController.java index ca6e4e5a..114f239d 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/paperquestion/controller/PaperQuestionController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/paperquestion/controller/PaperQuestionController.java @@ -89,7 +89,7 @@ public class PaperQuestionController extends JeecgController add(@RequestBody PaperQuestion paperQuestion) { paperQuestionService.save(paperQuestion); - return Result.OK("添加成功!"); + return Result.OK(paperQuestion.getId()); } /** diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/question/controller/QuestionController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/question/controller/QuestionController.java index 08921abe..33f3fbb2 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/question/controller/QuestionController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-learn/src/main/java/org/jeecg/modules/gen/question/controller/QuestionController.java @@ -91,7 +91,7 @@ public class QuestionController extends JeecgController add(@RequestBody Question question) { questionService.save(question); - return Result.OK("添加成功!"); + return Result.OK(question.getId()); } /**