merge
This commit is contained in:
GoCo 2025-09-01 16:11:21 +08:00
commit cff6d963a0
2 changed files with 262 additions and 81 deletions

View File

@ -199,6 +199,8 @@ public class AiolExamController extends JeecgController<AiolExam, IAiolExamServi
private IAiolQuestionRepoService questionRepoService; private IAiolQuestionRepoService questionRepoService;
@Autowired @Autowired
private IAiolQuestionOptionService questionOptionService; private IAiolQuestionOptionService questionOptionService;
@Autowired
private IAiolQuestionAnswerService questionAnswerService;
//获取考试试题 //获取考试试题
@RequestMapping("/getExamQuestions/{examId}") @RequestMapping("/getExamQuestions/{examId}")
@ -242,7 +244,7 @@ public class AiolExamController extends JeecgController<AiolExam, IAiolExamServi
List<AiolQuestion> questions = questionService.listByIds(questionIds); List<AiolQuestion> questions = questionService.listByIds(questionIds);
List<AiolQuestion> sortedQuestions = questions; List<AiolQuestion> sortedQuestions = questions;
// 试题内容按试卷中的顺序排序 // 固定试题内容按试卷中的顺序排序
if(paper.getGenerateMode()==0){ if(paper.getGenerateMode()==0){
Map<String, AiolQuestion> questionMap = questions.stream() Map<String, AiolQuestion> questionMap = questions.stream()
.collect(Collectors.toMap(AiolQuestion::getId, question -> question)); .collect(Collectors.toMap(AiolQuestion::getId, question -> question));
@ -251,35 +253,9 @@ public class AiolExamController extends JeecgController<AiolExam, IAiolExamServi
.map(paperQuestion -> questionMap.get(paperQuestion.getQuestionId())) .map(paperQuestion -> questionMap.get(paperQuestion.getQuestionId()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
//获取复选题id
List<String> type5Ids = sortedQuestions.stream()
.filter(question -> question.getType() == 5)
.map(AiolQuestion::getId)
.collect(Collectors.toList());
//获取复选题所包含的题目
List<AiolQuestion> type5Questions = new ArrayList<>();
if (!type5Ids.isEmpty()) {
type5Questions = questionService.list(
new LambdaQueryWrapper<AiolQuestion>()
.in(AiolQuestion::getParentId, type5Ids)
);
}
// 创建一个映射用于快速查找父ID对应的子题目
Map<String, List<AiolQuestion>> parentToChildrenMap = type5Questions.stream()
.collect(Collectors.groupingBy(AiolQuestion::getParentId));
// 将子题目添加到原始列表中保持原有顺序
List<AiolQuestion> resultQuestions = new ArrayList<>();
for (AiolQuestion question : questions) {
resultQuestions.add(question);
if (question.getType() == 5) {
List<AiolQuestion> children = parentToChildrenMap.getOrDefault(question.getId(), Collections.emptyList());
resultQuestions.addAll(children);
}
}
//创建考试答题初始化记录 //创建考试答题初始化记录
List<AiolExamAnswer> examAnswerList = new ArrayList<>(); List<AiolExamAnswer> examAnswerList = new ArrayList<>();
for (AiolQuestion resultQuestion : resultQuestions) { for (AiolQuestion resultQuestion : sortedQuestions) {
AiolExamAnswer examAnswer = new AiolExamAnswer(); AiolExamAnswer examAnswer = new AiolExamAnswer();
examAnswer.setExamId(examId); examAnswer.setExamId(examId);
examAnswer.setUserId(studentId); examAnswer.setUserId(studentId);
@ -297,7 +273,7 @@ public class AiolExamController extends JeecgController<AiolExam, IAiolExamServi
examRecord.setStatus(0); examRecord.setStatus(0);
examRecordService.save(examRecord); examRecordService.save(examRecord);
// 返回排序后的试题总列表 // 返回排序后的试题总列表
return Result.OK(resultQuestions); return Result.OK(sortedQuestions);
} }
@PostMapping("/submitAnswer") @PostMapping("/submitAnswer")
@ -393,19 +369,44 @@ public class AiolExamController extends JeecgController<AiolExam, IAiolExamServi
// 根据规则随机抽取题目 // 根据规则随机抽取题目
List<String> questionIds = new ArrayList<>(); List<String> questionIds = new ArrayList<>();
List<String> compositeQuestions = new ArrayList<>(); // 存储复合题ID
for (Integer type : typeQuestions.keySet()) { for (Integer type : typeQuestions.keySet()) {
int count = ruleJson.getInteger("type"+type + "_count"); // 例如single_choice_count int count = ruleJson.getInteger("type" + type + "_count");
List<String> typeQuestionIds = typeQuestions.get(type); List<String> typeQuestionIds = typeQuestions.get(type);
// 随机抽取指定数量的题目 // 随机抽取指定数量的题目
if (typeQuestionIds.size() <= count) { if (typeQuestionIds.size() <= count) {
questionIds.addAll(typeQuestionIds); if (type == 5) { // 复合题先不添加等处理完其他题目后再添加
compositeQuestions.addAll(typeQuestionIds);
} else {
questionIds.addAll(typeQuestionIds);
}
} else { } else {
// 打乱顺序后取前count个 // 打乱顺序后取前count个
Collections.shuffle(typeQuestionIds); Collections.shuffle(typeQuestionIds);
questionIds.addAll(typeQuestionIds.subList(0, count)); List<String> 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<AiolQuestion>()
.eq(AiolQuestion::getParentId, compositeId)
).stream()
.map(subQuestion -> subQuestion.getId().toString())
.forEach(questionIds::add);
}
return questionIds; return questionIds;
} }
@ -442,84 +443,212 @@ public class AiolExamController extends JeecgController<AiolExam, IAiolExamServi
* @return 阅卷后的答题列表 * @return 阅卷后的答题列表
*/ */
private List<AiolExamAnswer> gradeExam(String examId, String userId) { private List<AiolExamAnswer> gradeExam(String examId, String userId) {
//获取试卷信息
AiolExam exam = examService.getById(examId);
//获取组卷信息
AiolPaper paper = paperService.getOne(new LambdaQueryWrapper<AiolPaper>().eq(AiolPaper::getId, exam.getPaperId()));
if(paper == null){
throw new RuntimeException("试卷不存在");
}
Map<String, Double> questionScoreMap = new HashMap<>();
JSONObject ruleJson = null;
if(paper.getGenerateMode()==0){
List<AiolPaperQuestion> list = paperQuestionService.list(new LambdaQueryWrapper<AiolPaperQuestion>().eq(AiolPaperQuestion::getPaperId, paper.getId()));
questionScoreMap = list.stream()
.collect(Collectors.toMap(
AiolPaperQuestion::getQuestionId,
AiolPaperQuestion::getScore
));
}else {
ruleJson = JSON.parseObject(paper.getRules());
}
// 获取学生的答题列表 // 获取学生的答题列表
List<AiolExamAnswer> examAnswerList = examAnswerService.list(new LambdaQueryWrapper<AiolExamAnswer>() List<AiolExamAnswer> examAnswerList = examAnswerService.list(
.eq(AiolExamAnswer::getExamId, examId) new LambdaQueryWrapper<AiolExamAnswer>()
.eq(AiolExamAnswer::getUserId, userId) .eq(AiolExamAnswer::getExamId, examId)
.eq(AiolExamAnswer::getUserId, userId)
); );
// 提取所有题目ID // 提取所有题目ID
List<String> questionIds = examAnswerList.stream() List<String> questionIds = examAnswerList.stream()
.map(AiolExamAnswer::getQuestionId) .map(AiolExamAnswer::getQuestionId)
.collect(Collectors.toList()); .collect(Collectors.toList());
// 查询题目 // 查询题目
List<AiolQuestion> questions = questionService.list(new LambdaQueryWrapper<AiolQuestion>() List<AiolQuestion> questions = questionService.list(
.in(AiolQuestion::getId, questionIds) new LambdaQueryWrapper<AiolQuestion>()
.lt(AiolQuestion::getType, 3) .in(AiolQuestion::getId, questionIds)
.lt(AiolQuestion::getType, 4)
); );
// 创建题目ID到题目的映射 // 创建题目ID到题目的映射
Map<String, AiolQuestion> questionMap = questions.stream() Map<String, AiolQuestion> questionMap = questions.stream()
.collect(Collectors.toMap(AiolQuestion::getId, question -> question)); .collect(Collectors.toMap(AiolQuestion::getId, question -> question));
// 获取 选择多选判断 题目ID列表 // 按题目类型分组
List<String> questionIdsFromQuestions = questions.stream() Map<Integer, List<AiolQuestion>> questionsByType = questions.stream()
.map(AiolQuestion::getId) .collect(Collectors.groupingBy(AiolQuestion::getType));
// 分离选择题012和填空题3
List<AiolQuestion> choiceQuestions = questionsByType.entrySet().stream()
.filter(entry -> entry.getKey() < 3)
.flatMap(entry -> entry.getValue().stream())
.collect(Collectors.toList()); .collect(Collectors.toList());
// 查询这些题目的正确选项 List<AiolQuestion> fillQuestions = questionsByType.getOrDefault(3, new ArrayList<>());
List<AiolQuestionOption> questionOptions = questionOptionService.list(new LambdaQueryWrapper<AiolQuestionOption>()
.in(AiolQuestionOption::getQuestionId, questionIdsFromQuestions) // 查询题目的正确选项
.eq(AiolQuestionOption::getIzCorrent, 1) List<AiolQuestionOption> questionOptions = questionOptionService.list(
new LambdaQueryWrapper<AiolQuestionOption>()
.in(AiolQuestionOption::getQuestionId,
choiceQuestions.stream()
.map(AiolQuestion::getId)
.collect(Collectors.toList()))
.eq(AiolQuestionOption::getIzCorrent, 1)
); );
// 将选项转换为Map结构为题目ID -> 正确答案选项ID列表 // 查询填空题的正确答案
List<AiolQuestionAnswer> questionAnswers = questionAnswerService.list(
new LambdaQueryWrapper<AiolQuestionAnswer>()
.in(AiolQuestionAnswer::getQuestionId,
fillQuestions.stream()
.map(AiolQuestion::getId)
.collect(Collectors.toList()))
);
// 将选择题的正确选项转换为Map结构为题目ID -> 正确答案选项ID列表
Map<String, List<String>> correctAnswerMap = questionOptions.stream() Map<String, List<String>> correctAnswerMap = questionOptions.stream()
.collect(Collectors.groupingBy( .collect(Collectors.groupingBy(
AiolQuestionOption::getQuestionId, AiolQuestionOption::getQuestionId,
Collectors.mapping(AiolQuestionOption::getId, Collectors.toList()) Collectors.mapping(AiolQuestionOption::getId, Collectors.toList())
)); ));
// 遍历学生的答案进行评分 // 将填空题的正确答案转换为Map结构为题目ID -> (空序号 -> 答案列表)
Map<String, Map<Integer, List<String>>> fillAnswerMap = questionAnswers.stream()
.collect(Collectors.groupingBy(
AiolQuestionAnswer::getQuestionId,
Collectors.groupingBy(
AiolQuestionAnswer::getOrderNo,
Collectors.mapping(AiolQuestionAnswer::getAnswerText, Collectors.toList())
)
));
// 遍历学生的答案进行评分
for (AiolExamAnswer examAnswer : examAnswerList) { for (AiolExamAnswer examAnswer : examAnswerList) {
String studentAnswer = examAnswer.getAnswer(); String studentAnswer = examAnswer.getAnswer();
AiolQuestion question = questionMap.get(examAnswer.getQuestionId()); AiolQuestion question = questionMap.get(examAnswer.getQuestionId());
List<String> correctAnswers = correctAnswerMap.get(examAnswer.getQuestionId());
// 比较答案并设置分数 if (studentAnswer == null || question == null) {
if (studentAnswer != null && question != null && correctAnswers != null) { examAnswer.setScore(0.0);
// 将学生答案按逗号分割成列表 examAnswer.setIzCorrect(0);
List<String> studentAnswers = Arrays.asList(studentAnswer.split(",")); continue;
double score = 0.0; }
// 根据题目类型进行评分 List<String> studentAnswers = Arrays.asList(studentAnswer.split(","));
if (question.getType() == 1 || question.getType() == 2) { // 单选题或判断题 double score = 0.0;
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(); // 使用题目设定的分值 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");
} }
} }
} break;
examAnswer.setIzCorrect(score> 0.0 ? 1 : 0); case 2: // 判断题
examAnswer.setScore(score); if (studentAnswers.get(0).equals(correctAnswerMap.get(question.getId()).get(0))) {
} else { if (paper.getGenerateMode() == 0) {
examAnswer.setScore(0.0); // 答案为空或题目不存在 score = questionScoreMap.get(question.getId());
} else {
assert ruleJson != null;
score = ruleJson.getDouble("type" + question.getType() + "_score");
}
}
break;
case 1: // 多选题
List<String> 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<Integer, List<String>> 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<String> 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; return examAnswerList;
} }
//获取设备信息 //获取设备信息
public String getDeviceInfo(HttpServletRequest request) { public String getDeviceInfo(HttpServletRequest request) {
// 获取User-Agent // 获取User-Agent
String userAgent = request.getHeader("User-Agent"); String userAgent = request.getHeader("User-Agent");

View File

@ -1,20 +1,22 @@
package org.jeecg.modules.aiol.controller; package org.jeecg.modules.aiol.controller;
import java.util.Arrays; import java.math.BigDecimal;
import java.util.HashMap; import java.math.RoundingMode;
import java.util.List; import java.util.*;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum; import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.aiol.entity.AiolPaperQuestion; import org.jeecg.modules.aiol.entity.AiolPaperQuestion;
import org.jeecg.modules.aiol.entity.AiolQuestion;
import org.jeecg.modules.aiol.service.IAiolPaperQuestionService; import org.jeecg.modules.aiol.service.IAiolPaperQuestionService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.aiol.service.IAiolQuestionService;
import org.jeecgframework.poi.excel.ExcelImportUtil; import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants; import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams; 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.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.jeecg.common.system.base.controller.JeecgController; import org.jeecg.common.system.base.controller.JeecgController;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartHttpServletRequest;
@ -51,6 +55,8 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
public class AiolPaperQuestionController extends JeecgController<AiolPaperQuestion, IAiolPaperQuestionService> { public class AiolPaperQuestionController extends JeecgController<AiolPaperQuestion, IAiolPaperQuestionService> {
@Autowired @Autowired
private IAiolPaperQuestionService aiolPaperQuestionService; private IAiolPaperQuestionService aiolPaperQuestionService;
@Autowired
private IAiolQuestionService aiolQuestionService;
/** /**
* 分页列表查询 * 分页列表查询
@ -87,9 +93,41 @@ public class AiolPaperQuestionController extends JeecgController<AiolPaperQuesti
@RequiresPermissions("aiol:aiol_paper_question:add") @RequiresPermissions("aiol:aiol_paper_question:add")
@PostMapping(value = "/add") @PostMapping(value = "/add")
public Result<String> add(@RequestBody AiolPaperQuestion aiolPaperQuestion) { public Result<String> add(@RequestBody AiolPaperQuestion aiolPaperQuestion) {
aiolPaperQuestionService.save(aiolPaperQuestion); boolean isScoreNaN = false;
//获取试题及其子题目分数
List<AiolPaperQuestion> 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<AiolQuestion> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AiolQuestion::getParentId, question.getId());
List<AiolQuestion> 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<AiolPaperQuesti
@Operation(summary="试卷试题-通过id删除") @Operation(summary="试卷试题-通过id删除")
@RequiresPermissions("aiol:aiol_paper_question:delete") @RequiresPermissions("aiol:aiol_paper_question:delete")
@DeleteMapping(value = "/delete") @DeleteMapping(value = "/delete")
@Transactional
public Result<String> delete(@RequestParam(name="id",required=true) String id) { public Result<String> delete(@RequestParam(name="id",required=true) String id) {
AiolPaperQuestion byId = aiolPaperQuestionService.getById(id);
//如果试题类型为复合题则删除其子题目
List<AiolQuestion> list = aiolQuestionService.list(
new LambdaQueryWrapper<AiolQuestion>().
eq(AiolQuestion::getParentId, byId.getQuestionId()));
if(!list.isEmpty()){
aiolPaperQuestionService.remove(
new LambdaQueryWrapper<AiolPaperQuestion>().
eq(AiolPaperQuestion::getPaperId, byId.getPaperId())
.in(AiolPaperQuestion::getQuestionId, list.stream().map(AiolQuestion::getId).collect(Collectors.toList()))
);
}
aiolPaperQuestionService.removeById(id); aiolPaperQuestionService.removeById(id);
return Result.OK("删除成功!"); return Result.OK("删除成功!");
} }