From c4a6307b9ac2499862a6c4ccb0891bd598fa32e9 Mon Sep 17 00:00:00 2001 From: Lqc Date: Wed, 3 Sep 2025 15:27:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiol/controller/AiolExamController.java | 1540 ++++++++--------- 1 file changed, 770 insertions(+), 770 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 fad257b2..b5a340c6 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 @@ -61,132 +61,132 @@ public class AiolExamController extends JeecgController> queryPageList(AiolExam aiolExam, -// @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, -// @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, -// HttpServletRequest req) { -// -// -// QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(aiolExam, req.getParameterMap()); -// Page page = new Page(pageNo, pageSize); -// IPage pageList = aiolExamService.page(page, queryWrapper); -// return Result.OK(pageList); -// } -// -// /** -// * 添加 -// * -// * @param aiolExam -// * @return -// */ -// @AutoLog(value = "考试-添加") -// @Operation(summary="考试-添加") -// @RequiresPermissions("aiol:aiol_exam:add") -// @PostMapping(value = "/add") -// public Result add(@RequestBody AiolExam aiolExam) { -// aiolExamService.save(aiolExam); -// -// return Result.OK("添加成功!"); -// } -// -// /** -// * 编辑 -// * -// * @param aiolExam -// * @return -// */ -// @AutoLog(value = "考试-编辑") -// @Operation(summary="考试-编辑") -// @RequiresPermissions("aiol:aiol_exam:edit") -// @RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST}) -// public Result edit(@RequestBody AiolExam aiolExam) { -// aiolExamService.updateById(aiolExam); -// return Result.OK("编辑成功!"); -// } -// -// /** -// * 通过id删除 -// * -// * @param id -// * @return -// */ -// @AutoLog(value = "考试-通过id删除") -// @Operation(summary="考试-通过id删除") -// @RequiresPermissions("aiol:aiol_exam:delete") -// @DeleteMapping(value = "/delete") -// public Result delete(@RequestParam(name="id",required=true) String id) { -// aiolExamService.removeById(id); -// return Result.OK("删除成功!"); -// } -// -// /** -// * 批量删除 -// * -// * @param ids -// * @return -// */ -// @AutoLog(value = "考试-批量删除") -// @Operation(summary="考试-批量删除") -// @RequiresPermissions("aiol:aiol_exam:deleteBatch") -// @DeleteMapping(value = "/deleteBatch") -// public Result deleteBatch(@RequestParam(name="ids",required=true) String ids) { -// this.aiolExamService.removeByIds(Arrays.asList(ids.split(","))); -// return Result.OK("批量删除成功!"); -// } -// -// /** -// * 通过id查询 -// * -// * @param id -// * @return -// */ -// //@AutoLog(value = "考试-通过id查询") -// @Operation(summary="考试-通过id查询") -// @GetMapping(value = "/queryById") -// public Result queryById(@RequestParam(name="id",required=true) String id) { -// AiolExam aiolExam = aiolExamService.getById(id); -// if(aiolExam==null) { -// return Result.error("未找到对应数据"); -// } -// return Result.OK(aiolExam); -// } -// -// /** -// * 导出excel -// * -// * @param request -// * @param aiolExam -// */ -// @RequiresPermissions("aiol:aiol_exam:exportXls") -// @RequestMapping(value = "/exportXls") -// public ModelAndView exportXls(HttpServletRequest request, AiolExam aiolExam) { -// return super.exportXls(request, aiolExam, AiolExam.class, "考试"); -// } -// -// /** -// * 通过excel导入数据 -// * -// * @param request -// * @param response -// * @return -// */ -// @RequiresPermissions("aiol:aiol_exam:importExcel") -// @RequestMapping(value = "/importExcel", method = RequestMethod.POST) -// public Result importExcel(HttpServletRequest request, HttpServletResponse response) { -// return super.importExcel(request, response, AiolExam.class); -// } + /** + * 分页列表查询 + * + * @param aiolExam + * @param pageNo + * @param pageSize + * @param req + * @return + */ + //@AutoLog(value = "考试-分页列表查询") + @Operation(summary="考试-分页列表查询") + @GetMapping(value = "/list") + public Result> queryPageList(AiolExam aiolExam, + @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, + @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, + HttpServletRequest req) { + + + QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(aiolExam, req.getParameterMap()); + Page page = new Page(pageNo, pageSize); + IPage pageList = aiolExamService.page(page, queryWrapper); + return Result.OK(pageList); + } + + /** + * 添加 + * + * @param aiolExam + * @return + */ + @AutoLog(value = "考试-添加") + @Operation(summary="考试-添加") + @RequiresPermissions("aiol:aiol_exam:add") + @PostMapping(value = "/add") + public Result add(@RequestBody AiolExam aiolExam) { + aiolExamService.save(aiolExam); + + return Result.OK("添加成功!"); + } + + /** + * 编辑 + * + * @param aiolExam + * @return + */ + @AutoLog(value = "考试-编辑") + @Operation(summary="考试-编辑") + @RequiresPermissions("aiol:aiol_exam:edit") + @RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST}) + public Result edit(@RequestBody AiolExam aiolExam) { + aiolExamService.updateById(aiolExam); + return Result.OK("编辑成功!"); + } + + /** + * 通过id删除 + * + * @param id + * @return + */ + @AutoLog(value = "考试-通过id删除") + @Operation(summary="考试-通过id删除") + @RequiresPermissions("aiol:aiol_exam:delete") + @DeleteMapping(value = "/delete") + public Result delete(@RequestParam(name="id",required=true) String id) { + aiolExamService.removeById(id); + return Result.OK("删除成功!"); + } + + /** + * 批量删除 + * + * @param ids + * @return + */ + @AutoLog(value = "考试-批量删除") + @Operation(summary="考试-批量删除") + @RequiresPermissions("aiol:aiol_exam:deleteBatch") + @DeleteMapping(value = "/deleteBatch") + public Result deleteBatch(@RequestParam(name="ids",required=true) String ids) { + this.aiolExamService.removeByIds(Arrays.asList(ids.split(","))); + return Result.OK("批量删除成功!"); + } + + /** + * 通过id查询 + * + * @param id + * @return + */ + //@AutoLog(value = "考试-通过id查询") + @Operation(summary="考试-通过id查询") + @GetMapping(value = "/queryById") + public Result queryById(@RequestParam(name="id",required=true) String id) { + AiolExam aiolExam = aiolExamService.getById(id); + if(aiolExam==null) { + return Result.error("未找到对应数据"); + } + return Result.OK(aiolExam); + } + + /** + * 导出excel + * + * @param request + * @param aiolExam + */ + @RequiresPermissions("aiol:aiol_exam:exportXls") + @RequestMapping(value = "/exportXls") + public ModelAndView exportXls(HttpServletRequest request, AiolExam aiolExam) { + return super.exportXls(request, aiolExam, AiolExam.class, "考试"); + } + + /** + * 通过excel导入数据 + * + * @param request + * @param response + * @return + */ + @RequiresPermissions("aiol:aiol_exam:importExcel") + @RequestMapping(value = "/importExcel", method = RequestMethod.POST) + public Result importExcel(HttpServletRequest request, HttpServletResponse response) { + return super.importExcel(request, response, AiolExam.class); + } @Autowired private IAiolPaperService paperService; @@ -207,303 +207,303 @@ public class AiolExamController extends JeecgController getExamQuestions(@PathVariable String examId, @RequestParam String studentId) { -// AiolExam exam = examService.getById(examId); -// if(exam.getPaperId().isEmpty()){ -// return Result.error("考试未关联试卷"); -// } -// AiolPaper paper = paperService.getById(exam.getPaperId()); -// //题目id集合 -// List questionIds; -// List paperQuestions = new ArrayList<>(); -// //随机组卷 -// if(paper.getGenerateMode()==1){ -// List list = questionRepoService.list( -// new LambdaQueryWrapper(). -// eq(AiolQuestionRepo::getRepoId, paper.getRepoId()) -// ); -// //筛选试卷 -// questionIds = random(list, paper.getRules()); -// }else { -// //固定组卷 -// // 获取试卷中的试题关联信息 -// paperQuestions = paperQuestionService.getQuestions(paper.getId()); -// -// // 如果没有试题关联信息,直接返回空列表 -// if (CollectionUtils.isEmpty(paperQuestions)) { -// return Result.OK("试卷中没有试题"); -// } -// // 提取所有试题ID -// questionIds = paperQuestions.stream() -// .map(AiolPaperQuestion::getQuestionId) -// .collect(Collectors.toList()); -// } -// if (CollectionUtils.isEmpty(questionIds)) { -// return Result.OK("试卷中没有试题"); -// } -// // 从题目表查询试题内容 -// List questions = questionService.listByIds(questionIds); -// -// List sortedQuestions = questions; -// // 将固定试题内容按试卷中的顺序排序 -// if(paper.getGenerateMode()==0){ -// Map questionMap = questions.stream() -// .collect(Collectors.toMap(AiolQuestion::getId, question -> question)); -// sortedQuestions = paperQuestions.stream() -// .sorted(Comparator.comparing(AiolPaperQuestion::getOrderNo)) -// .map(paperQuestion -> questionMap.get(paperQuestion.getQuestionId())) -// .collect(Collectors.toList()); -// } -// //创建考试答题初始化记录 -// List examAnswerList = new ArrayList<>(); -// for (AiolQuestion resultQuestion : sortedQuestions) { -// AiolExamAnswer examAnswer = new AiolExamAnswer(); -// examAnswer.setExamId(examId); -// examAnswer.setUserId(studentId); -// if (resultQuestion != null && StringUtils.isNotEmpty(resultQuestion.getParentId())) { -// examAnswer.setParentQuestionId(resultQuestion.getParentId()); -// } -// examAnswer.setQuestionId(resultQuestion.getId()); -// examAnswerList.add(examAnswer); -// } -// examAnswerService.saveBatch(examAnswerList); -// //创建考试记录 -// AiolExamRecord examRecord = new AiolExamRecord(); -// examRecord.setExamId(examId); -// examRecord.setUserId(studentId); -// examRecord.setStatus(0); -// examRecordService.save(examRecord); -// // 返回排序后的试题总列表 -// return Result.OK(sortedQuestions); -// } -// -// @PostMapping("/submitAnswer") -// @Operation(summary = "提交答案") -// public Result submitAnswer(@RequestBody AiolExamAnswer examAnswer) { -// // 创建查询条件 -// LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); -// updateWrapper.eq(AiolExamAnswer::getExamId, examAnswer.getExamId()) -// .eq(AiolExamAnswer::getUserId, examAnswer.getUserId()) -// .eq(AiolExamAnswer::getQuestionId, examAnswer.getQuestionId()); -// -// // 更新答案 -// if (examAnswer.getAnswer() != null) { -// updateWrapper.set(AiolExamAnswer::getAnswer, examAnswer.getAnswer()); -// } -// // 执行更新 -// return examAnswerService.update(updateWrapper) ? Result.OK() : Result.error("提交答案失败"); -// } -// -// @PostMapping("/submitExam") -// @Operation(summary = "提交考试") -// @Transactional -// public Result submitExam(@RequestBody AiolExamRecord examRecord, -// HttpServletRequest req) { -// examRecord.setIpAddress(getClientIp(req)); -// examRecord.setDeviceInfo(getDeviceInfo(req)); -// //修改状态 -// examRecord.setStatus(1); -// // 创建更新条件 -// LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); -// updateWrapper.eq(AiolExamRecord::getExamId, examRecord.getExamId()) -// .eq(AiolExamRecord::getUserId, examRecord.getUserId()); -// // 更新ip -// if (examRecord.getIpAddress() != null) { -// updateWrapper.set(AiolExamRecord::getIpAddress, examRecord.getIpAddress()); -// } -// // 更新设备信息 -// if (examRecord.getDeviceInfo() != null) { -// updateWrapper.set(AiolExamRecord::getDeviceInfo, examRecord.getDeviceInfo()); -// } -// // 阅卷 -// List gradedAnswers = gradeExam(examRecord.getExamId(), examRecord.getUserId()); -// examAnswerService.updateBatchById(gradedAnswers); -// // 更新考试状态,提交时间 -// updateWrapper. -// set(AiolExamRecord::getStatus,1). -// set(AiolExamRecord::getSubmittedAt, new Date()); -// // 更新 -// return examRecordService.update(updateWrapper) ? Result.OK() : Result.error("提交考试失败"); -// } -// -// //获取名下考试信息 -// @GetMapping("/getExamInfo") -// @Operation(summary = "获取教师名下考试") -// public Result getExamInfo(@RequestParam String userId) { -// return Result.OK(examService.list(new LambdaQueryWrapper().eq(AiolExam::getCreateBy, userId))); -// } -// -// //获取考试相关答卷 -// @GetMapping("/getExamRecord") -// @Operation(summary = "获取考试相关答卷记录") -// public Result getExamRecord(@RequestParam String examId) { -// return Result.OK(examRecordService. -// list(new LambdaQueryWrapper(). -// eq(AiolExamRecord::getExamId, examId))); -// } -// -// //获取考试相关答卷 -// @GetMapping("/getExamRecordByUser") -// @Operation(summary = "获取考试相关答卷答案") -// public Result getExamRecordByUser(@RequestParam String examId,@RequestParam String userId) { -// List list = examAnswerService. -// list(new LambdaQueryWrapper(). -// eq(AiolExamAnswer::getExamId, examId). -// eq(AiolExamAnswer::getUserId, userId)); -// Map questionIdToAnswerMap = list.stream() -// .collect(Collectors.toMap( -// AiolExamAnswer::getQuestionId, -// answer -> answer -// )); -// if(!list.isEmpty()){ -// List questionIds = list.stream() -// .map(AiolExamAnswer::getQuestionId) -// .collect(Collectors.toList()); -// List questions = questionService.listByIds(questionIds); -// //取出简答题 -// List type4Questions = questions.stream() -// .filter(question -> question.getType() == 4) -// .collect(Collectors.toList()); -// Map questionIdToTitleMap = type4Questions.stream().collect(Collectors.toMap(AiolQuestion::getId, question -> question)); -// if(!type4Questions.isEmpty()){ -// List collect = type4Questions.stream().map(AiolQuestion::getId).collect(Collectors.toList()); -// //查询题目答案 -// List list1 = questionAnswerService.list(new LambdaQueryWrapper() -// .in(AiolQuestionAnswer::getQuestionId, collect) -// ); -// // 查询题目分数 -// AiolExam exam = examService.getById(examId); -// AiolPaper paper = paperService.getById(exam.getPaperId()); -// -// // 根据试卷生成模式获取分数 -// Map questionIdToScoreMap = Optional.ofNullable(paper) -// .filter(p -> p.getGenerateMode() == 0) -// .map(p -> paperQuestionService.list(new LambdaQueryWrapper() -// .eq(AiolPaperQuestion::getPaperId, p.getId()) -// .in(AiolPaperQuestion::getQuestionId, collect))) -// .orElse(Collections.emptyList()) -// .stream() -// .collect(Collectors.toMap( -// AiolPaperQuestion::getQuestionId, -// AiolPaperQuestion::getScore -// )); -// -// // 获取类型4的题目分数(仅在非固定模式下) -// Double type4Score = Optional.ofNullable(paper) -// .filter(p -> p.getGenerateMode() != 0) -// .map(p -> JSON.parseObject(p.getRules()).getDouble("type4_score")) -// .orElse(0.0); -// -// // 构建用户答案列表 -// List questionAnswerUserList = list1.stream() -// .map(questionAnswer -> { -// QuestionAnswerUser questionAnswerUser = new QuestionAnswerUser(); -// questionAnswerUser.setQuestion(questionIdToTitleMap.get(questionAnswer.getQuestionId())); -// questionAnswerUser.setUserAnswer(questionIdToAnswerMap.get(questionAnswer.getQuestionId())); -// questionAnswerUser.setAnswer(questionAnswer); -// questionAnswerUser.setFullMark(questionIdToScoreMap.getOrDefault( -// questionAnswer.getQuestionId(), -// type4Score -// )); -// return questionAnswerUser; -// }) -// .collect(Collectors.toList()); -// return Result.OK(questionAnswerUserList); -// } -// } -// return Result.OK("没有需要批改的题目数据"); -// } -// -// @PostMapping("/submitGrade") -// @Operation(summary = "提交批改") -// @Transactional -// public Result submitGrade(@RequestBody List examAnswers) { -// // 检查输入参数是否为空 -// if (CollectionUtils.isEmpty(examAnswers)) { -// return Result.error("批改数据不能为空"); -// } -// try { -// String examId = examAnswers.get(0).getExamId(); -// String userId = examAnswers.get(0).getUserId(); -// List list = examAnswerService.list(new LambdaQueryWrapper() -// .eq(AiolExamAnswer::getExamId, examId) -// .eq(AiolExamAnswer::getUserId, userId)); -// // 创建examAnswers的ID映射,方便查找 -// Map examAnswerMap = examAnswers.stream() -// .collect(Collectors.toMap(AiolExamAnswer::getId, answer -> answer)); -// // 创建list的id映射 -// Map listIdMap = list.stream() -// .collect(Collectors.toMap(AiolExamAnswer::getQuestionId, answer -> answer)); -// //计算总分 -// Double totalScore = list.stream().mapToDouble(AiolExamAnswer::getScore).sum() + examAnswers.stream().mapToDouble(AiolExamAnswer::getScore).sum(); -// -// // 复合题对错得分 -// List sonList = list.stream() -// .filter(answer -> answer.getParentQuestionId() != null && !answer.getParentQuestionId().isEmpty()) -// .collect(Collectors.toList()); -// -// if (!sonList.isEmpty()) { -// // 按父ID分组 -// Map> parentGroup = sonList.stream() -// .collect(Collectors.groupingBy(AiolExamAnswer::getParentQuestionId)); -// -// // 遍历每个父题组 -// for (Map.Entry> entry : parentGroup.entrySet()) { -// String parentId = entry.getKey(); -// List children = entry.getValue(); -// -// Double parentScore = 0.0; -// boolean isCorrect = true; -// -// // 计算每个子题的分数和正确性 -// for (AiolExamAnswer childAnswer : children) { -// Double sonScore = 0.0; -// if (examAnswerMap.get(childAnswer.getId()) != null) { -// if (isCorrect && examAnswerMap.get(childAnswer.getId()).getIzCorrect() == 0) { -// isCorrect = false; -// } -// sonScore = examAnswerMap.get(childAnswer.getId()).getScore(); -// } else { -// if (isCorrect && childAnswer.getIzCorrect() == 0) { -// isCorrect = false; -// } -// sonScore = childAnswer.getScore(); -// } -// parentScore += sonScore; -// } -// -// // 更新父题的分数和正确性 -// AiolExamAnswer parentAnswer = listIdMap.get(parentId); -// if (parentAnswer != null) { -// parentAnswer.setIzCorrect(isCorrect ? 1 : 0); -// parentAnswer.setScore(parentScore); -// examAnswers.add(parentAnswer); -// } -// } -// } -// // 更新考试记录的状态为已批改 -// examRecordService.update(new LambdaUpdateWrapper() -// .set(AiolExamRecord::getStatus, 2) -// .set(AiolExamRecord::getTotalScore, totalScore) -// .eq(AiolExamRecord::getExamId, examId) -// .eq(AiolExamRecord::getUserId, userId)); -// // 批量更新答题分数 -// boolean success = examAnswerService.updateBatchById(examAnswers); -// if (!success) { -// return Result.error("批改失败"); -// } -// // 可选:更新考试的总分和平均分(如果需要) -// // 这里可以添加额外的业务逻辑 -// return Result.OK("批改成功"); -// } catch (Exception e) { -// log.error("提交批改失败", e); -// return Result.error("系统错误:" + e.getMessage()); -// } -// } + //获取考试试题 + @RequestMapping("/getExamQuestions/{examId}") + @Operation(summary = "获取考试试题") + @Transactional + public Result getExamQuestions(@PathVariable String examId, @RequestParam String studentId) { + AiolExam exam = examService.getById(examId); + if(exam.getPaperId().isEmpty()){ + return Result.error("考试未关联试卷"); + } + AiolPaper paper = paperService.getById(exam.getPaperId()); + //题目id集合 + List questionIds; + List paperQuestions = new ArrayList<>(); + //随机组卷 + if(paper.getGenerateMode()==1){ + List list = questionRepoService.list( + new LambdaQueryWrapper(). + eq(AiolQuestionRepo::getRepoId, paper.getRepoId()) + ); + //筛选试卷 + questionIds = random(list, paper.getRules()); + }else { + //固定组卷 + // 获取试卷中的试题关联信息 + paperQuestions = paperQuestionService.getQuestions(paper.getId()); + + // 如果没有试题关联信息,直接返回空列表 + if (CollectionUtils.isEmpty(paperQuestions)) { + return Result.OK("试卷中没有试题"); + } + // 提取所有试题ID + questionIds = paperQuestions.stream() + .map(AiolPaperQuestion::getQuestionId) + .collect(Collectors.toList()); + } + if (CollectionUtils.isEmpty(questionIds)) { + return Result.OK("试卷中没有试题"); + } + // 从题目表查询试题内容 + List questions = questionService.listByIds(questionIds); + + List sortedQuestions = questions; + // 将固定试题内容按试卷中的顺序排序 + if(paper.getGenerateMode()==0){ + Map questionMap = questions.stream() + .collect(Collectors.toMap(AiolQuestion::getId, question -> question)); + sortedQuestions = paperQuestions.stream() + .sorted(Comparator.comparing(AiolPaperQuestion::getOrderNo)) + .map(paperQuestion -> questionMap.get(paperQuestion.getQuestionId())) + .collect(Collectors.toList()); + } + //创建考试答题初始化记录 + List examAnswerList = new ArrayList<>(); + for (AiolQuestion resultQuestion : sortedQuestions) { + AiolExamAnswer examAnswer = new AiolExamAnswer(); + examAnswer.setExamId(examId); + examAnswer.setUserId(studentId); + if (resultQuestion != null && StringUtils.isNotEmpty(resultQuestion.getParentId())) { + examAnswer.setParentQuestionId(resultQuestion.getParentId()); + } + examAnswer.setQuestionId(resultQuestion.getId()); + examAnswerList.add(examAnswer); + } + examAnswerService.saveBatch(examAnswerList); + //创建考试记录 + AiolExamRecord examRecord = new AiolExamRecord(); + examRecord.setExamId(examId); + examRecord.setUserId(studentId); + examRecord.setStatus(0); + examRecordService.save(examRecord); + // 返回排序后的试题总列表 + return Result.OK(sortedQuestions); + } + + @PostMapping("/submitAnswer") + @Operation(summary = "提交答案") + public Result submitAnswer(@RequestBody AiolExamAnswer examAnswer) { + // 创建查询条件 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(AiolExamAnswer::getExamId, examAnswer.getExamId()) + .eq(AiolExamAnswer::getUserId, examAnswer.getUserId()) + .eq(AiolExamAnswer::getQuestionId, examAnswer.getQuestionId()); + + // 更新答案 + if (examAnswer.getAnswer() != null) { + updateWrapper.set(AiolExamAnswer::getAnswer, examAnswer.getAnswer()); + } + // 执行更新 + return examAnswerService.update(updateWrapper) ? Result.OK() : Result.error("提交答案失败"); + } + + @PostMapping("/submitExam") + @Operation(summary = "提交考试") + @Transactional + public Result submitExam(@RequestBody AiolExamRecord examRecord, + HttpServletRequest req) { + examRecord.setIpAddress(getClientIp(req)); + examRecord.setDeviceInfo(getDeviceInfo(req)); + //修改状态 + examRecord.setStatus(1); + // 创建更新条件 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(AiolExamRecord::getExamId, examRecord.getExamId()) + .eq(AiolExamRecord::getUserId, examRecord.getUserId()); + // 更新ip + if (examRecord.getIpAddress() != null) { + updateWrapper.set(AiolExamRecord::getIpAddress, examRecord.getIpAddress()); + } + // 更新设备信息 + if (examRecord.getDeviceInfo() != null) { + updateWrapper.set(AiolExamRecord::getDeviceInfo, examRecord.getDeviceInfo()); + } + // 阅卷 + List gradedAnswers = gradeExam(examRecord.getExamId(), examRecord.getUserId()); + examAnswerService.updateBatchById(gradedAnswers); + // 更新考试状态,提交时间 + updateWrapper. + set(AiolExamRecord::getStatus,1). + set(AiolExamRecord::getSubmittedAt, new Date()); + // 更新 + return examRecordService.update(updateWrapper) ? Result.OK() : Result.error("提交考试失败"); + } + + //获取名下考试信息 + @GetMapping("/getExamInfo") + @Operation(summary = "获取教师名下考试") + public Result getExamInfo(@RequestParam String userId) { + return Result.OK(examService.list(new LambdaQueryWrapper().eq(AiolExam::getCreateBy, userId))); + } + + //获取考试相关答卷 + @GetMapping("/getExamRecord") + @Operation(summary = "获取考试相关答卷记录") + public Result getExamRecord(@RequestParam String examId) { + return Result.OK(examRecordService. + list(new LambdaQueryWrapper(). + eq(AiolExamRecord::getExamId, examId))); + } + + //获取考试相关答卷 + @GetMapping("/getExamRecordByUser") + @Operation(summary = "获取考试相关答卷答案") + public Result getExamRecordByUser(@RequestParam String examId,@RequestParam String userId) { + List list = examAnswerService. + list(new LambdaQueryWrapper(). + eq(AiolExamAnswer::getExamId, examId). + eq(AiolExamAnswer::getUserId, userId)); + Map questionIdToAnswerMap = list.stream() + .collect(Collectors.toMap( + AiolExamAnswer::getQuestionId, + answer -> answer + )); + if(!list.isEmpty()){ + List questionIds = list.stream() + .map(AiolExamAnswer::getQuestionId) + .collect(Collectors.toList()); + List questions = questionService.listByIds(questionIds); + //取出简答题 + List type4Questions = questions.stream() + .filter(question -> question.getType() == 4) + .collect(Collectors.toList()); + Map questionIdToTitleMap = type4Questions.stream().collect(Collectors.toMap(AiolQuestion::getId, question -> question)); + if(!type4Questions.isEmpty()){ + List collect = type4Questions.stream().map(AiolQuestion::getId).collect(Collectors.toList()); + //查询题目答案 + List list1 = questionAnswerService.list(new LambdaQueryWrapper() + .in(AiolQuestionAnswer::getQuestionId, collect) + ); + // 查询题目分数 + AiolExam exam = examService.getById(examId); + AiolPaper paper = paperService.getById(exam.getPaperId()); + + // 根据试卷生成模式获取分数 + Map questionIdToScoreMap = Optional.ofNullable(paper) + .filter(p -> p.getGenerateMode() == 0) + .map(p -> paperQuestionService.list(new LambdaQueryWrapper() + .eq(AiolPaperQuestion::getPaperId, p.getId()) + .in(AiolPaperQuestion::getQuestionId, collect))) + .orElse(Collections.emptyList()) + .stream() + .collect(Collectors.toMap( + AiolPaperQuestion::getQuestionId, + AiolPaperQuestion::getScore + )); + + // 获取类型4的题目分数(仅在非固定模式下) + Double type4Score = Optional.ofNullable(paper) + .filter(p -> p.getGenerateMode() != 0) + .map(p -> JSON.parseObject(p.getRules()).getDouble("type4_score")) + .orElse(0.0); + + // 构建用户答案列表 + List questionAnswerUserList = list1.stream() + .map(questionAnswer -> { + QuestionAnswerUser questionAnswerUser = new QuestionAnswerUser(); + questionAnswerUser.setQuestion(questionIdToTitleMap.get(questionAnswer.getQuestionId())); + questionAnswerUser.setUserAnswer(questionIdToAnswerMap.get(questionAnswer.getQuestionId())); + questionAnswerUser.setAnswer(questionAnswer); + questionAnswerUser.setFullMark(questionIdToScoreMap.getOrDefault( + questionAnswer.getQuestionId(), + type4Score + )); + return questionAnswerUser; + }) + .collect(Collectors.toList()); + return Result.OK(questionAnswerUserList); + } + } + return Result.OK("没有需要批改的题目数据"); + } + + @PostMapping("/submitGrade") + @Operation(summary = "提交批改") + @Transactional + public Result submitGrade(@RequestBody List examAnswers) { + // 检查输入参数是否为空 + if (CollectionUtils.isEmpty(examAnswers)) { + return Result.error("批改数据不能为空"); + } + try { + String examId = examAnswers.get(0).getExamId(); + String userId = examAnswers.get(0).getUserId(); + List list = examAnswerService.list(new LambdaQueryWrapper() + .eq(AiolExamAnswer::getExamId, examId) + .eq(AiolExamAnswer::getUserId, userId)); + // 创建examAnswers的ID映射,方便查找 + Map examAnswerMap = examAnswers.stream() + .collect(Collectors.toMap(AiolExamAnswer::getId, answer -> answer)); + // 创建list的id映射 + Map listIdMap = list.stream() + .collect(Collectors.toMap(AiolExamAnswer::getQuestionId, answer -> answer)); + //计算总分 + Double totalScore = list.stream().mapToDouble(AiolExamAnswer::getScore).sum() + examAnswers.stream().mapToDouble(AiolExamAnswer::getScore).sum(); + + // 复合题对错得分 + List sonList = list.stream() + .filter(answer -> answer.getParentQuestionId() != null && !answer.getParentQuestionId().isEmpty()) + .collect(Collectors.toList()); + + if (!sonList.isEmpty()) { + // 按父ID分组 + Map> parentGroup = sonList.stream() + .collect(Collectors.groupingBy(AiolExamAnswer::getParentQuestionId)); + + // 遍历每个父题组 + for (Map.Entry> entry : parentGroup.entrySet()) { + String parentId = entry.getKey(); + List children = entry.getValue(); + + Double parentScore = 0.0; + boolean isCorrect = true; + + // 计算每个子题的分数和正确性 + for (AiolExamAnswer childAnswer : children) { + Double sonScore = 0.0; + if (examAnswerMap.get(childAnswer.getId()) != null) { + if (isCorrect && examAnswerMap.get(childAnswer.getId()).getIzCorrect() == 0) { + isCorrect = false; + } + sonScore = examAnswerMap.get(childAnswer.getId()).getScore(); + } else { + if (isCorrect && childAnswer.getIzCorrect() == 0) { + isCorrect = false; + } + sonScore = childAnswer.getScore(); + } + parentScore += sonScore; + } + + // 更新父题的分数和正确性 + AiolExamAnswer parentAnswer = listIdMap.get(parentId); + if (parentAnswer != null) { + parentAnswer.setIzCorrect(isCorrect ? 1 : 0); + parentAnswer.setScore(parentScore); + examAnswers.add(parentAnswer); + } + } + } + // 更新考试记录的状态为已批改 + examRecordService.update(new LambdaUpdateWrapper() + .set(AiolExamRecord::getStatus, 2) + .set(AiolExamRecord::getTotalScore, totalScore) + .eq(AiolExamRecord::getExamId, examId) + .eq(AiolExamRecord::getUserId, userId)); + // 批量更新答题分数 + boolean success = examAnswerService.updateBatchById(examAnswers); + if (!success) { + return Result.error("批改失败"); + } + // 可选:更新考试的总分和平均分(如果需要) + // 这里可以添加额外的业务逻辑 + return Result.OK("批改成功"); + } catch (Exception e) { + log.error("提交批改失败", e); + return Result.error("系统错误:" + e.getMessage()); + } + } @@ -649,351 +649,351 @@ public class AiolExamController extends JeecgController random(List list, String rules) { -// JSONObject ruleJson = JSON.parseObject(rules); -// -// // 根据规则筛选和随机抽取题目 -// Map> typeQuestions = new HashMap<>(); -// -// // 先按题目类型分组 -// list.forEach(qr -> { -// AiolQuestion 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<>(); -// List compositeQuestions = new ArrayList<>(); // 存储复合题ID -// -// for (Integer type : typeQuestions.keySet()) { -// int count = ruleJson.getInteger("type" + type + "_count"); -// List typeQuestionIds = typeQuestions.get(type); -// -// // 随机抽取指定数量的题目 -// if (typeQuestionIds.size() <= count) { -// if (type == 5) { // 复合题先不添加,等处理完其他题目后再添加 -// compositeQuestions.addAll(typeQuestionIds); -// } else { -// questionIds.addAll(typeQuestionIds); -// } -// } else { -// // 打乱顺序后取前count个 -// Collections.shuffle(typeQuestionIds); -// 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; -// } -// -// //获取ip信息 -// public String getClientIp(HttpServletRequest request) { -// String ip = request.getHeader("X-Forwarded-For"); -// -// if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { -// ip = request.getHeader("Proxy-Client-IP"); -// } -// if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { -// ip = request.getHeader("WL-Proxy-Client-IP"); -// } -// if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { -// ip = request.getHeader("HTTP_CLIENT_IP"); -// } -// if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { -// ip = request.getHeader("HTTP_X_FORWARDED_FOR"); -// } -// if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { -// ip = request.getRemoteAddr(); -// } -// // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 -// if (ip != null && ip.contains(",")) { -// ip = ip.split(",")[0].trim(); -// } -// return ip; -// } -// -// /** -// * 批量阅卷 -// * @param examId 考试ID -// * @param userId 用户ID -// * @return 阅卷后的答题列表 -// */ -// private List 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) -// ); -// -// // 提取所有题目ID -// List questionIds = examAnswerList.stream() -// .map(AiolExamAnswer::getQuestionId) -// .collect(Collectors.toList()); -// -// // 查询题目 -// List questions = questionService.list( -// new LambdaQueryWrapper() -// .in(AiolQuestion::getId, questionIds) -// .lt(AiolQuestion::getType, 4) -// ); -// -// // 创建题目ID到题目的映射 -// Map questionMap = questions.stream() -// .collect(Collectors.toMap(AiolQuestion::getId, question -> question)); -// -// // 按题目类型分组 -// 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 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) -// ); -// -// // 查询填空题的正确答案 -// 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()); -// -// if (studentAnswer == null || question == null) { -// examAnswer.setScore(0.0); -// examAnswer.setIzCorrect(0); -// continue; -// } -// -// List studentAnswers = Arrays.asList(studentAnswer.split(",")); -// double score = 0.0; -// -// // 根据题目类型进行评分 -// 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; -// 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"); -// // 解析设备信息 -// String deviceInfo = parseDeviceInfo(userAgent); -// return deviceInfo; -// } -// private String parseDeviceInfo(String userAgent) { -// if (userAgent == null) { -// return "Unknown"; -// } -// StringBuilder deviceInfo = new StringBuilder(); -// // 判断操作系统 -// if (userAgent.indexOf("Windows") > -1) { -// deviceInfo.append("Windows"); -// } else if (userAgent.indexOf("Mac") > -1) { -// deviceInfo.append("Mac"); -// } else if (userAgent.indexOf("X11") > -1) { -// deviceInfo.append("Unix"); -// } else if (userAgent.indexOf("Android") > -1) { -// deviceInfo.append("Android"); -// } else if (userAgent.indexOf("iPhone") > -1 || userAgent.indexOf("iPad") > -1) { -// deviceInfo.append("iOS"); -// } else { -// deviceInfo.append("Unknown OS"); -// } -// deviceInfo.append(" | "); -// // 判断浏览器 -// if (userAgent.indexOf("MSIE") > -1) { -// deviceInfo.append("MSIE"); -// } else if (userAgent.indexOf("Firefox") > -1) { -// deviceInfo.append("Firefox"); -// } else if (userAgent.indexOf("Chrome") > -1) { -// deviceInfo.append("Chrome"); -// } else if (userAgent.indexOf("Safari") > -1) { -// deviceInfo.append("Safari"); -// } else if (userAgent.indexOf("Opera") > -1) { -// deviceInfo.append("Opera"); -// } else { -// deviceInfo.append("Unknown Browser"); -// } -// // 可以添加更多设备信息的判断,如: -// // 判断是否是移动设备 -// boolean isMobile = userAgent.indexOf("Mobile") > -1 || -// userAgent.indexOf("Android") > -1 || -// userAgent.indexOf("iPhone") > -1; -// deviceInfo.append(" | ").append(isMobile ? "Mobile" : "PC"); -// return deviceInfo.toString(); -// } + //根据考试规则随机组卷 + public List random(List list, String rules) { + JSONObject ruleJson = JSON.parseObject(rules); + + // 根据规则筛选和随机抽取题目 + Map> typeQuestions = new HashMap<>(); + + // 先按题目类型分组 + list.forEach(qr -> { + AiolQuestion 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<>(); + List compositeQuestions = new ArrayList<>(); // 存储复合题ID + + for (Integer type : typeQuestions.keySet()) { + int count = ruleJson.getInteger("type" + type + "_count"); + List typeQuestionIds = typeQuestions.get(type); + + // 随机抽取指定数量的题目 + if (typeQuestionIds.size() <= count) { + if (type == 5) { // 复合题先不添加,等处理完其他题目后再添加 + compositeQuestions.addAll(typeQuestionIds); + } else { + questionIds.addAll(typeQuestionIds); + } + } else { + // 打乱顺序后取前count个 + Collections.shuffle(typeQuestionIds); + 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; + } + + //获取ip信息 + public String getClientIp(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if (ip != null && ip.contains(",")) { + ip = ip.split(",")[0].trim(); + } + return ip; + } + + /** + * 批量阅卷 + * @param examId 考试ID + * @param userId 用户ID + * @return 阅卷后的答题列表 + */ + private List 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) + ); + + // 提取所有题目ID + List questionIds = examAnswerList.stream() + .map(AiolExamAnswer::getQuestionId) + .collect(Collectors.toList()); + + // 查询题目 + List questions = questionService.list( + new LambdaQueryWrapper() + .in(AiolQuestion::getId, questionIds) + .lt(AiolQuestion::getType, 4) + ); + + // 创建题目ID到题目的映射 + Map questionMap = questions.stream() + .collect(Collectors.toMap(AiolQuestion::getId, question -> question)); + + // 按题目类型分组 + 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 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) + ); + + // 查询填空题的正确答案 + 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()); + + if (studentAnswer == null || question == null) { + examAnswer.setScore(0.0); + examAnswer.setIzCorrect(0); + continue; + } + + List studentAnswers = Arrays.asList(studentAnswer.split(",")); + double score = 0.0; + + // 根据题目类型进行评分 + 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; + 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"); + // 解析设备信息 + String deviceInfo = parseDeviceInfo(userAgent); + return deviceInfo; + } + private String parseDeviceInfo(String userAgent) { + if (userAgent == null) { + return "Unknown"; + } + StringBuilder deviceInfo = new StringBuilder(); + // 判断操作系统 + if (userAgent.indexOf("Windows") > -1) { + deviceInfo.append("Windows"); + } else if (userAgent.indexOf("Mac") > -1) { + deviceInfo.append("Mac"); + } else if (userAgent.indexOf("X11") > -1) { + deviceInfo.append("Unix"); + } else if (userAgent.indexOf("Android") > -1) { + deviceInfo.append("Android"); + } else if (userAgent.indexOf("iPhone") > -1 || userAgent.indexOf("iPad") > -1) { + deviceInfo.append("iOS"); + } else { + deviceInfo.append("Unknown OS"); + } + deviceInfo.append(" | "); + // 判断浏览器 + if (userAgent.indexOf("MSIE") > -1) { + deviceInfo.append("MSIE"); + } else if (userAgent.indexOf("Firefox") > -1) { + deviceInfo.append("Firefox"); + } else if (userAgent.indexOf("Chrome") > -1) { + deviceInfo.append("Chrome"); + } else if (userAgent.indexOf("Safari") > -1) { + deviceInfo.append("Safari"); + } else if (userAgent.indexOf("Opera") > -1) { + deviceInfo.append("Opera"); + } else { + deviceInfo.append("Unknown Browser"); + } + // 可以添加更多设备信息的判断,如: + // 判断是否是移动设备 + boolean isMobile = userAgent.indexOf("Mobile") > -1 || + userAgent.indexOf("Android") > -1 || + userAgent.indexOf("iPhone") > -1; + deviceInfo.append(" | ").append(isMobile ? "Mobile" : "PC"); + return deviceInfo.toString(); + } }