From 44ee564b5fd6481863451e751a74bdbfa44875e3 Mon Sep 17 00:00:00 2001 From: Lqc Date: Mon, 1 Sep 2025 16:01:34 +0800 Subject: [PATCH] =?UTF-8?q?=E8=80=83=E8=AF=95=E5=AE=A2=E8=A7=82=E9=A2=98?= =?UTF-8?q?=E6=89=B9=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiol/controller/AiolExamController.java | 279 +++++++++++++----- .../AiolPaperQuestionController.java | 64 +++- 2 files changed, 262 insertions(+), 81 deletions(-) diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolExamController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolExamController.java index cd15d64c..4f0c021a 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolExamController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolExamController.java @@ -199,6 +199,8 @@ public class AiolExamController extends JeecgController questions = questionService.listByIds(questionIds); List sortedQuestions = questions; - // 将试题内容按试卷中的顺序排序 + // 将固定试题内容按试卷中的顺序排序 if(paper.getGenerateMode()==0){ Map questionMap = questions.stream() .collect(Collectors.toMap(AiolQuestion::getId, question -> question)); @@ -251,35 +253,9 @@ public class AiolExamController extends JeecgController questionMap.get(paperQuestion.getQuestionId())) .collect(Collectors.toList()); } - //获取复选题id - List type5Ids = sortedQuestions.stream() - .filter(question -> question.getType() == 5) - .map(AiolQuestion::getId) - .collect(Collectors.toList()); - //获取复选题所包含的题目 - List type5Questions = new ArrayList<>(); - if (!type5Ids.isEmpty()) { - type5Questions = questionService.list( - new LambdaQueryWrapper() - .in(AiolQuestion::getParentId, type5Ids) - ); - } - // 创建一个映射,用于快速查找父ID对应的子题目 - Map> parentToChildrenMap = type5Questions.stream() - .collect(Collectors.groupingBy(AiolQuestion::getParentId)); - - // 将子题目添加到原始列表中,保持原有顺序 - List resultQuestions = new ArrayList<>(); - for (AiolQuestion question : questions) { - resultQuestions.add(question); - if (question.getType() == 5) { - List children = parentToChildrenMap.getOrDefault(question.getId(), Collections.emptyList()); - resultQuestions.addAll(children); - } - } //创建考试答题初始化记录 List examAnswerList = new ArrayList<>(); - for (AiolQuestion resultQuestion : resultQuestions) { + for (AiolQuestion resultQuestion : sortedQuestions) { AiolExamAnswer examAnswer = new AiolExamAnswer(); examAnswer.setExamId(examId); examAnswer.setUserId(studentId); @@ -297,7 +273,7 @@ public class AiolExamController extends JeecgController questionIds = new ArrayList<>(); + List compositeQuestions = new ArrayList<>(); // 存储复合题ID + for (Integer type : typeQuestions.keySet()) { - int count = ruleJson.getInteger("type"+type + "_count"); // 例如:single_choice_count + int count = ruleJson.getInteger("type" + type + "_count"); List typeQuestionIds = typeQuestions.get(type); // 随机抽取指定数量的题目 if (typeQuestionIds.size() <= count) { - questionIds.addAll(typeQuestionIds); + if (type == 5) { // 复合题先不添加,等处理完其他题目后再添加 + compositeQuestions.addAll(typeQuestionIds); + } else { + questionIds.addAll(typeQuestionIds); + } } else { // 打乱顺序后取前count个 Collections.shuffle(typeQuestionIds); - questionIds.addAll(typeQuestionIds.subList(0, count)); + List selectedQuestions = typeQuestionIds.subList(0, count); + + if (type == 5) { // 复合题先不添加,等处理完其他题目后再添加 + compositeQuestions.addAll(selectedQuestions); + } else { + questionIds.addAll(selectedQuestions); + } } } + + // 将复合题及其子题目添加到列表末尾 + for (String compositeId : compositeQuestions) { + questionIds.add(compositeId); + // 获取并添加子题目ID + questionService.list( + new LambdaQueryWrapper() + .eq(AiolQuestion::getParentId, compositeId) + ).stream() + .map(subQuestion -> subQuestion.getId().toString()) + .forEach(questionIds::add); + } + return questionIds; } @@ -442,84 +443,212 @@ public class AiolExamController extends JeecgController gradeExam(String examId, String userId) { + //获取试卷信息 + AiolExam exam = examService.getById(examId); + //获取组卷信息 + AiolPaper paper = paperService.getOne(new LambdaQueryWrapper().eq(AiolPaper::getId, exam.getPaperId())); + if(paper == null){ + throw new RuntimeException("试卷不存在"); + } + Map questionScoreMap = new HashMap<>(); + JSONObject ruleJson = null; + if(paper.getGenerateMode()==0){ + List list = paperQuestionService.list(new LambdaQueryWrapper().eq(AiolPaperQuestion::getPaperId, paper.getId())); + questionScoreMap = list.stream() + .collect(Collectors.toMap( + AiolPaperQuestion::getQuestionId, + AiolPaperQuestion::getScore + )); + }else { + ruleJson = JSON.parseObject(paper.getRules()); + } // 获取学生的答题列表 - List examAnswerList = examAnswerService.list(new LambdaQueryWrapper() - .eq(AiolExamAnswer::getExamId, examId) - .eq(AiolExamAnswer::getUserId, userId) + List examAnswerList = examAnswerService.list( + new LambdaQueryWrapper() + .eq(AiolExamAnswer::getExamId, examId) + .eq(AiolExamAnswer::getUserId, userId) ); - // 提取所有题目ID + // 提取所有题目ID List questionIds = examAnswerList.stream() .map(AiolExamAnswer::getQuestionId) .collect(Collectors.toList()); - // 查询题目 - List questions = questionService.list(new LambdaQueryWrapper() - .in(AiolQuestion::getId, questionIds) - .lt(AiolQuestion::getType, 3) + // 查询题目 + List questions = questionService.list( + new LambdaQueryWrapper() + .in(AiolQuestion::getId, questionIds) + .lt(AiolQuestion::getType, 4) ); - // 创建题目ID到题目的映射 + // 创建题目ID到题目的映射 Map questionMap = questions.stream() .collect(Collectors.toMap(AiolQuestion::getId, question -> question)); - // 获取 选择、多选、判断 题目ID列表 - List questionIdsFromQuestions = questions.stream() - .map(AiolQuestion::getId) + // 按题目类型分组 + Map> questionsByType = questions.stream() + .collect(Collectors.groupingBy(AiolQuestion::getType)); + + // 分离选择题(0、1、2)和填空题(3) + List choiceQuestions = questionsByType.entrySet().stream() + .filter(entry -> entry.getKey() < 3) + .flatMap(entry -> entry.getValue().stream()) .collect(Collectors.toList()); - // 查询这些题目的正确选项 - List questionOptions = questionOptionService.list(new LambdaQueryWrapper() - .in(AiolQuestionOption::getQuestionId, questionIdsFromQuestions) - .eq(AiolQuestionOption::getIzCorrent, 1) + List fillQuestions = questionsByType.getOrDefault(3, new ArrayList<>()); + + // 查询题目的正确选项 + List questionOptions = questionOptionService.list( + new LambdaQueryWrapper() + .in(AiolQuestionOption::getQuestionId, + choiceQuestions.stream() + .map(AiolQuestion::getId) + .collect(Collectors.toList())) + .eq(AiolQuestionOption::getIzCorrent, 1) ); - // 将选项转换为Map,结构为:题目ID -> 正确答案选项ID列表 + // 查询填空题的正确答案 + List questionAnswers = questionAnswerService.list( + new LambdaQueryWrapper() + .in(AiolQuestionAnswer::getQuestionId, + fillQuestions.stream() + .map(AiolQuestion::getId) + .collect(Collectors.toList())) + ); + + // 将选择题的正确选项转换为Map,结构为:题目ID -> 正确答案选项ID列表 Map> correctAnswerMap = questionOptions.stream() .collect(Collectors.groupingBy( AiolQuestionOption::getQuestionId, Collectors.mapping(AiolQuestionOption::getId, Collectors.toList()) )); - // 遍历学生的答案,进行评分 + // 将填空题的正确答案转换为Map,结构为:题目ID -> (空序号 -> 答案列表) + Map>> fillAnswerMap = questionAnswers.stream() + .collect(Collectors.groupingBy( + AiolQuestionAnswer::getQuestionId, + Collectors.groupingBy( + AiolQuestionAnswer::getOrderNo, + Collectors.mapping(AiolQuestionAnswer::getAnswerText, Collectors.toList()) + ) + )); + + // 遍历学生的答案,进行评分 for (AiolExamAnswer examAnswer : examAnswerList) { String studentAnswer = examAnswer.getAnswer(); AiolQuestion 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 (studentAnswer == null || question == null) { + examAnswer.setScore(0.0); + examAnswer.setIzCorrect(0); + continue; + } - // 根据题目类型进行评分 - 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); + List studentAnswers = Arrays.asList(studentAnswer.split(",")); + double score = 0.0; - if (allCorrect) { - score = question.getScore(); // 使用题目设定的分值 + // 根据题目类型进行评分 + switch (question.getType()) { + case 0: // 单选题 + if (studentAnswers.get(0).equals(correctAnswerMap.get(question.getId()).get(0))) { + if (paper.getGenerateMode() == 0) { + score = questionScoreMap.get(question.getId()); + } else { + assert ruleJson != null; + score = ruleJson.getDouble("type" + question.getType() + "_score"); } } - } - examAnswer.setIzCorrect(score> 0.0 ? 1 : 0); - examAnswer.setScore(score); - } else { - examAnswer.setScore(0.0); // 答案为空或题目不存在 + break; + case 2: // 判断题 + if (studentAnswers.get(0).equals(correctAnswerMap.get(question.getId()).get(0))) { + if (paper.getGenerateMode() == 0) { + score = questionScoreMap.get(question.getId()); + } else { + assert ruleJson != null; + score = ruleJson.getDouble("type" + question.getType() + "_score"); + } + } + break; + + case 1: // 多选题 + List correctChoiceAnswers = correctAnswerMap.get(question.getId()); + if (correctChoiceAnswers != null && !correctChoiceAnswers.isEmpty()) { + // 检查学生答案是否包含错误选项 + boolean hasWrongAnswer = studentAnswers.stream() + .anyMatch(answer -> !correctChoiceAnswers.contains(answer)); + + // 如果有错选,直接得0分 + if (hasWrongAnswer) { + score = 0.0; + } + // 如果没有错选,检查是否全对 + else { + boolean allCorrect = new HashSet<>(studentAnswers).containsAll(correctChoiceAnswers); + boolean sameCount = studentAnswers.size() == correctChoiceAnswers.size(); + + if (allCorrect && sameCount) { + // 全对且数量正确,得满分 + if (paper.getGenerateMode() == 0) { + score = questionScoreMap.get(question.getId()); + } else { + assert ruleJson != null; + score = ruleJson.getDouble("type" + question.getType() + "_score"); + } + } else if (studentAnswers.size() < correctChoiceAnswers.size()) { + // 少选,得一半分数 + if (paper.getGenerateMode() == 0) { + score = questionScoreMap.get(question.getId()) * 0.5; + } else { + assert ruleJson != null; + score = ruleJson.getDouble("type" + question.getType() + "_score") * 0.5; + } + } + } + } + break; + + case 3: // 填空题 + Map> correctFillAnswers = fillAnswerMap.get(question.getId()); + if (correctFillAnswers != null && !correctFillAnswers.isEmpty()) { + int totalBlanks = correctFillAnswers.size(); + int correctBlanks = 0; + + // 检查每个空的答案 + for (int i = 0; i < studentAnswers.size() && i < totalBlanks; i++) { + int blankNumber = i + 1; + List correctBlankAnswers = correctFillAnswers.get(blankNumber); + if (correctBlankAnswers != null && correctBlankAnswers.contains(studentAnswers.get(i))) { + correctBlanks++; + } + } + + // 计算得分 + if (correctBlanks > 0) { + double fullScore; + if (paper.getGenerateMode() == 0) { + fullScore = questionScoreMap.get(question.getId()); + } else { + assert ruleJson != null; + fullScore = ruleJson.getDouble("type" + question.getType() + "_score"); + } + + // 按正确空的数量比例给分 + score = fullScore * ((double) correctBlanks / totalBlanks); + } + } + break; } + examAnswer.setScore(score); + // 只有得了满分才算对 + examAnswer.setIzCorrect(score > 0 && score == (paper.getGenerateMode() == 0 ? + questionScoreMap.get(question.getId()) : + ruleJson.getDouble("type" + question.getType() + "_score")) ? 1 : 0); } + return examAnswerList; } - //获取设备信息 + //获取设备信息 public String getDeviceInfo(HttpServletRequest request) { // 获取User-Agent String userAgent = request.getHeader("User-Agent"); diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolPaperQuestionController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolPaperQuestionController.java index 83178046..53167aa7 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolPaperQuestionController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-aiol/src/main/java/org/jeecg/modules/aiol/controller/AiolPaperQuestionController.java @@ -1,20 +1,22 @@ package org.jeecg.modules.aiol.controller; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; import java.util.stream.Collectors; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.jeecg.common.api.vo.Result; import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryRuleEnum; import org.jeecg.common.util.oConvertUtils; import org.jeecg.modules.aiol.entity.AiolPaperQuestion; +import org.jeecg.modules.aiol.entity.AiolQuestion; import org.jeecg.modules.aiol.service.IAiolPaperQuestionService; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; @@ -22,6 +24,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.extern.slf4j.Slf4j; +import org.jeecg.modules.aiol.service.IAiolQuestionService; import org.jeecgframework.poi.excel.ExcelImportUtil; import org.jeecgframework.poi.excel.def.NormalExcelConstants; import org.jeecgframework.poi.excel.entity.ExportParams; @@ -29,6 +32,7 @@ import org.jeecgframework.poi.excel.entity.ImportParams; import org.jeecgframework.poi.excel.view.JeecgEntityExcelView; import org.jeecg.common.system.base.controller.JeecgController; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; @@ -51,6 +55,8 @@ import org.apache.shiro.authz.annotation.RequiresPermissions; public class AiolPaperQuestionController extends JeecgController { @Autowired private IAiolPaperQuestionService aiolPaperQuestionService; + @Autowired + private IAiolQuestionService aiolQuestionService; /** * 分页列表查询 @@ -87,9 +93,41 @@ public class AiolPaperQuestionController extends JeecgController add(@RequestBody AiolPaperQuestion aiolPaperQuestion) { - aiolPaperQuestionService.save(aiolPaperQuestion); + boolean isScoreNaN = false; + //获取试题及其子题目,分数 + List paperQuestions = new ArrayList<>(); + AiolQuestion question = aiolQuestionService.getById(aiolPaperQuestion.getQuestionId()); + if(aiolPaperQuestion.getScore().isNaN()){ + isScoreNaN = true; + aiolPaperQuestion.setScore(Double.valueOf(question.getScore())); + } + paperQuestions.add(aiolPaperQuestion); + if( question.getType() == 5 ){ + //如果试题类型为复合题,则获取其子题目 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(AiolQuestion::getParentId, question.getId()); + List questions = aiolQuestionService.list(queryWrapper); + for(AiolQuestion q : questions){ + AiolPaperQuestion sonPaperQuestion = new AiolPaperQuestion(); + sonPaperQuestion.setPaperId(aiolPaperQuestion.getPaperId()); + sonPaperQuestion.setQuestionId(q.getId()); + sonPaperQuestion.setOrderNo(aiolPaperQuestion.getOrderNo()); + if(isScoreNaN){ + sonPaperQuestion.setScore(Double.valueOf(q.getScore())); + }else{ + // 计算原始分数 + double rawScore = aiolPaperQuestion.getScore() * q.getScore() / question.getScore(); + // 四舍五入保留两位小数 + BigDecimal bd = new BigDecimal(rawScore); + bd = bd.setScale(2, RoundingMode.HALF_UP); + sonPaperQuestion.setScore(bd.doubleValue()); + } + paperQuestions.add(sonPaperQuestion); + } + } + aiolPaperQuestionService.saveBatch(paperQuestions); - return Result.OK("添加成功!"); + return Result.OK(aiolPaperQuestion.getId()); } /** @@ -117,7 +155,21 @@ public class AiolPaperQuestionController extends JeecgController delete(@RequestParam(name="id",required=true) String id) { + AiolPaperQuestion byId = aiolPaperQuestionService.getById(id); + //如果试题类型为复合题,则删除其子题目 + List list = aiolQuestionService.list( + new LambdaQueryWrapper(). + eq(AiolQuestion::getParentId, byId.getQuestionId())); + if(!list.isEmpty()){ + aiolPaperQuestionService.remove( + new LambdaQueryWrapper(). + eq(AiolPaperQuestion::getPaperId, byId.getPaperId()) + .in(AiolPaperQuestion::getQuestionId, list.stream().map(AiolQuestion::getId).collect(Collectors.toList())) + ); + + } aiolPaperQuestionService.removeById(id); return Result.OK("删除成功!"); }