package com.ycl.jxkg.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ycl.jxkg.base.Result; import com.ycl.jxkg.base.SystemCode; import com.ycl.jxkg.constants.ExamScoreConstant; import com.ycl.jxkg.context.WebContext; import com.ycl.jxkg.domain.base.AbsVo; import com.ycl.jxkg.domain.entity.*; import com.ycl.jxkg.domain.exam.PaperFixQuestionDTO; import com.ycl.jxkg.domain.exam.PaperQuestion; import com.ycl.jxkg.domain.exam.PaperQuestionSettingDTO; import com.ycl.jxkg.domain.exam.PaperSettingItem; import com.ycl.jxkg.domain.form.AddTimeForm; import com.ycl.jxkg.domain.form.ExamForm; import com.ycl.jxkg.domain.form.ForceSubmitForm; import com.ycl.jxkg.domain.query.ExamQuery; import com.ycl.jxkg.domain.question.QuestionItemObject; import com.ycl.jxkg.domain.question.QuestionObject; import com.ycl.jxkg.domain.vo.*; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperMarkNavbarVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperMarkVO; import com.ycl.jxkg.enums.DeductTypeEnum; import com.ycl.jxkg.enums.ExamPaperTypeEnum; import com.ycl.jxkg.enums.QuestionTypeEnum; import com.ycl.jxkg.enums.WebsocketCommendEnum; import com.ycl.jxkg.enums.general.ClassesStatusEnum; import com.ycl.jxkg.enums.general.ExamStatusEnum; import com.ycl.jxkg.enums.general.ExamSubmitTempStatusEnum; import com.ycl.jxkg.mapper.*; import com.ycl.jxkg.rabbitmq.msg.ExamStatusMsg; import com.ycl.jxkg.rabbitmq.product.Producer; import com.ycl.jxkg.server.WebsocketServer; import com.ycl.jxkg.service.ExamPaperScoreService; import com.ycl.jxkg.service.ExamPaperService; import com.ycl.jxkg.service.ExamService; import com.ycl.jxkg.utils.DateTimeUtil; import com.ycl.jxkg.utils.PageUtil; import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.transaction.annotation.Transactional; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import java.util.stream.Collectors; /** * 考试 服务实现类 * * @author xp * @since 2024-06-11 */ @Service @RequiredArgsConstructor public class ExamServiceImpl extends ServiceImpl implements ExamService { private static final String ANSWER_SPLIT = ","; private final ExamMapper examMapper; private final WebContext webContext; private final QuestionMapper questionMapper; private final ExamSubmitTempMapper examSubmitTempMapper; private final ClassesUserMapper classesUserMapper; private final ExamPaperMapper examPaperMapper; private final ExamPaperService examPaperService; private final WebsocketServer websocketServer; private final UserMapper userMapper; private final ExamPaperScoreMapper examPaperScoreMapper; private final ExamPaperScoreService examPaperScoreService; private final Producer producer; /** * 添加 * * @param form * @return */ @Override public Result add(ExamForm form) { Exam entity = ExamForm.getEntityByForm(form, null); entity.setStatus(ExamStatusEnum.getStatusByTime(form.getStartTime(), form.getEndTime(), null)); entity.setTeacherId(webContext.getCurrentUser().getId()); // 设置乐观锁版本 entity.setUpdateVersion(0); if (baseMapper.insert(entity) > 0) { this.sendMQ(entity, 0); } return Result.ok("添加成功"); } /** * 修改 * * @param form * @return */ @Override public Result update(ExamForm form) { Exam entity = baseMapper.selectById(form.getId()); // 为空抛IllegalArgumentException,做全局异常处理 Assert.notNull(entity, "记录不存在"); // 判断考试状态 if (!ExamStatusEnum.NOT_START.equals(entity.getStatus())) { throw new RuntimeException("只能修改还未开始的考试"); } BeanUtils.copyProperties(form, entity); entity.setStatus(ExamStatusEnum.getStatusByTime(form.getStartTime(), form.getEndTime(), new Date())); // 如果修改成功发送mq消息 if (baseMapper.updateById(entity) > 0) { this.sendMQ(entity, entity.getUpdateVersion()); } return Result.ok("修改成功"); } /** * 发送mq消息 * * @param entity 考试实体类 * @param version 乐观锁版本 */ public void sendMQ(Exam entity, Integer version) { // 如果当前状态为未开始,则发送两条mq消息,一条设置状态为进行中,一条设置状态为已结束 if (ExamStatusEnum.NOT_START.equals(entity.getStatus())) { // 进行状态消息 ExamStatusMsg ingMsg = new ExamStatusMsg(); ingMsg.setVersion(version); ingMsg.setExamId(entity.getId()); ingMsg.setTargetStatus(ExamStatusEnum.ING); producer.examMsg(entity.getId(), JSON.toJSONString(ingMsg), DateTimeUtil.getTwoTimeDiffMS(entity.getStartTime(), new Date())); // 结束状态消息 ExamStatusMsg finishedMsg = new ExamStatusMsg(); finishedMsg.setVersion(version); finishedMsg.setExamId(entity.getId()); finishedMsg.setTargetStatus(ExamStatusEnum.FINISHED); producer.examMsg(entity.getId(), JSON.toJSONString(finishedMsg), DateTimeUtil.getTwoTimeDiffMS(entity.getEndTime(), new Date())); } else if (ExamStatusEnum.ING.equals(entity.getStatus())) { // 当前是进行中状态则只需发送结束消息 // 结束状态消息 ExamStatusMsg finishedMsg = new ExamStatusMsg(); finishedMsg.setVersion(0); finishedMsg.setExamId(entity.getId()); finishedMsg.setTargetStatus(ExamStatusEnum.FINISHED); producer.examMsg(entity.getId(), JSON.toJSONString(finishedMsg), DateTimeUtil.getTwoTimeDiffMS(entity.getEndTime(), new Date())); } } /** * 根据考试的当前状态,得到下一个状态 * * @param currentStatus * @return */ public ExamStatusEnum getNextStatus(ExamStatusEnum currentStatus) { if (ExamStatusEnum.NOT_START.equals(currentStatus)) { return ExamStatusEnum.ING; } else { return ExamStatusEnum.FINISHED; } } /** * 批量删除 * * @param ids * @return */ @Override public Result remove(List ids) { baseMapper.deleteBatchIds(ids); return Result.ok("删除成功"); } /** * id删除 * * @param id * @return */ @Override public Result removeById(String id) { baseMapper.deleteById(id); return Result.ok("删除成功"); } /** * 分页查询 * * @param query * @return */ @Override public Result page(ExamQuery query) { IPage page = PageUtil.getPage(query, ExamVO.class); baseMapper.getPage(page, query, webContext.getCurrentUser().getId()); page.getRecords().stream().forEach(item -> { if (!StringUtils.hasText(item.getClassName())) { item.setClassName("班级不存在或被删除"); item.setClassesId(null); } if (!StringUtils.hasText(item.getExamPaperName())) { item.setExamPaperName("试卷不存在或被删除"); item.setExamPaperId(null); } }); // return Result.ok().data(page.getRecords()).total(page.getTotal()); } @Override public Result studentPage(ExamQuery query) { IPage page = PageUtil.getPage(query, ExamVO.class); baseMapper.studentPage(page, query, webContext.getCurrentUser().getId()); return Result.ok().data(page.getRecords()).total(page.getTotal()); } @Override @Transactional(rollbackFor = Exception.class) public Result start(Integer id) { Exam exam = baseMapper.selectById(id); if (Objects.isNull(exam)) { throw new RuntimeException("该考试不存在"); } if (Objects.isNull(exam.getExamPaperId())) { throw new RuntimeException("考试未绑定试卷"); } if (ExamStatusEnum.NOT_START.equals(exam.getStatus())) { throw new RuntimeException("考试还未开始"); } if (ExamStatusEnum.FINISHED.equals(exam.getStatus())) { throw new RuntimeException("考试已经结束"); } // 查出考试试卷 ExamPaper examPaper = examPaperMapper.selectById(exam.getExamPaperId()); if (Objects.isNull(examPaper)) { throw new RuntimeException("试卷不存在"); } // 如果已经参加过考试,直接返回数据 ExamSubmitTemp hasJoin = new LambdaQueryChainWrapper<>(examSubmitTempMapper) .eq(ExamSubmitTemp::getExamId, id) .eq(ExamSubmitTemp::getUserId, webContext.getCurrentUser().getId()) .one(); if (Objects.nonNull(hasJoin)) { // 允许提交后继续作答 if(ExamSubmitTempStatusEnum.finish.equals(hasJoin.getStatus())){ throw new RuntimeException("您已提交试卷,请勿重复作答"); } StartExamVO startExamVO = new StartExamVO(); startExamVO.setExamName(exam.getExamName()); startExamVO.setId(hasJoin.getExamId()); startExamVO.setTitleList(JSON.parseArray(hasJoin.getExamSubmit(), PaperFixQuestionVO.class)); startExamVO.setSuggestTime(examPaper.getSuggestTime()); startExamVO.setDoTime(hasJoin.getDoTime()); return Result.ok(startExamVO); } // 响应数据 StartExamVO startExamVO = new StartExamVO(); startExamVO.setExamName(exam.getExamName()); startExamVO.setId(exam.getId()); startExamVO.setSuggestTime(examPaper.getSuggestTime()); // 试卷内容 List examData = new ArrayList<>(); // 拿到题目副本数据 List questionAnswerCopyVOList = new ArrayList<>(32); // 将题目转换为可临时保存的题目结构。固定试卷和随序试卷的题目是直接保存到content字段的 if (ExamPaperTypeEnum.Fixed.getCode().equals(examPaper.getPaperType()) || ExamPaperTypeEnum.RandomOrder.getCode().equals(examPaper.getPaperType())) { if (!StringUtils.hasText(examPaper.getContent())) { throw new RuntimeException("试卷题目为空"); } // 转换 examData = this.coverTo(examPaper, questionAnswerCopyVOList); } else if (ExamPaperTypeEnum.Random.getCode().equals(examPaper.getPaperType())) { // 根据随机试卷的配置,随机生成对应题目 if (!StringUtils.hasText(examPaper.getContent())) { throw new RuntimeException("试卷配置异常,请联系老师"); } List paperSettingList = JSON.parseArray(examPaper.getContent(), PaperQuestionSettingDTO.class); examData = new ArrayList<>(8); for (PaperQuestionSettingDTO paperSetting : paperSettingList) { PaperFixQuestionVO paperFixQuestionVO = new PaperFixQuestionVO(); paperFixQuestionVO.setTitle(paperSetting.getTitle()); paperFixQuestionVO.setQuestionType(paperSetting.getQuestionType()); //一个类型的题目list List childQuestionList = new ArrayList<>(); List settingList = paperSetting.getSettingList(); for (PaperSettingItem settingItem : settingList) { Integer num = settingItem.getNum(); Integer difficult = settingItem.getDifficult(); //需要配置的题目数量为0则跳过 if (num == null || num == 0) continue; List questions = questionMapper.getRandomQuestion(settingItem.getSubjectId(), paperSetting.getQuestionType(), difficult, settingItem.getNum()); if (org.springframework.util.CollectionUtils.isEmpty(questions) || settingItem.getNum() > questions.size()) { throw new RuntimeException("配置的题目数不足以生成试卷"); } // 拿到题目后组装为可临时保存的题目结构 List childQuestions = questions.stream().map(item -> { DoQuestionVO doQuestionVO = new DoQuestionVO(); doQuestionVO.setQuestionType(item.getQuestionType()); //从配置里拿题目分数 doQuestionVO.setQuestionScore(settingItem.getScore()); if (StringUtils.hasText(item.getContent())) { QuestionObject questionObject = JSON.parseObject(item.getContent(), QuestionObject.class); doQuestionVO.setQuestionItemList(questionObject.getQuestionItemObjects()); doQuestionVO.setTitle(questionObject.getTitleContent()); } doQuestionVO.setId(item.getId()); doQuestionVO.setOriginalFile(item.getOriginalFile()); doQuestionVO.setAudioFile(item.getAudioFile()); // 题目副本 QuestionAnswerCopyVO copy = new QuestionAnswerCopyVO(); copy.setId(item.getId()); copy.setDifficult(item.getDifficult()); copy.setAnalyze(JSON.parseObject(item.getContent(), PaperQuestion.class).getAnalyze()); //填空的答案在Json里 if (QuestionTypeEnum.GapFilling.getCode().equals(item.getQuestionType())) { List gapAnswer = new ArrayList<>(); for (QuestionItemObject questionItemObject : doQuestionVO.getQuestionItemList()) { gapAnswer.add(questionItemObject.getContent()); } copy.setCorrect(String.join(ANSWER_SPLIT, gapAnswer)); } else { copy.setCorrect(item.getCorrect()); } questionAnswerCopyVOList.add(copy); return doQuestionVO; }).collect(Collectors.toList()); //添加到这个类型的list中 childQuestionList.addAll(childQuestions); } paperFixQuestionVO.setQuestionList(childQuestionList); examData.add(paperFixQuestionVO); } } ExamSubmitTemp examSubmitTemp = new ExamSubmitTemp(); examSubmitTemp.setExamId(id); examSubmitTemp.setStatus(ExamSubmitTempStatusEnum.temp); examSubmitTemp.setExamSubmit(JSON.toJSONString(examData)); examSubmitTemp.setCreateTime(new Date()); examSubmitTemp.setUserId(webContext.getCurrentUser().getId()); examSubmitTemp.setMarkPaperStatus(ExamSubmitTempStatusEnum.temp); examSubmitTemp.setQuestionAnswerCopy(JSON.toJSONString(questionAnswerCopyVOList)); examSubmitTempMapper.insert(examSubmitTemp); startExamVO.setTitleList(examData); return Result.ok(startExamVO); } /** * 将数据库存储的题目,转为可临时保存的题目结构 * * @param examPaper 试卷 * @param questionAnswerCopyVOList 题目副本集合 * @return */ private List coverTo(ExamPaper examPaper, List questionAnswerCopyVOList) { if (!StringUtils.hasText(examPaper.getContent())) { throw new RuntimeException("试卷未配置题目,请联系老师"); } List questionWarpList = JSON.parseArray(examPaper.getContent(), PaperFixQuestionDTO.class); return questionWarpList.stream().map(item -> { PaperFixQuestionVO vo = new PaperFixQuestionVO(); vo.setTitle(item.getTitle()); vo.setQuestionType(item.getQuestionType()); List doQuestionVOS = item.getQuestionList().stream().map(question -> { DoQuestionVO doQuestionVO = new DoQuestionVO(); doQuestionVO.setTitle(question.getTitle()); doQuestionVO.setQuestionType(item.getQuestionType()); //增加题目分数 doQuestionVO.setQuestionScore(question.getScore()); // 题目副本 QuestionAnswerCopyVO copy = new QuestionAnswerCopyVO(); copy.setId(question.getId()); copy.setAnalyze(question.getAnalyze()); copy.setDifficult(question.getDifficult()); //填空的答案在Json里 if (QuestionTypeEnum.GapFilling.getCode().equals(item.getQuestionType())) { List gapAnswer = new ArrayList<>(); for (QuestionItemObject questionItemObject : question.getItems()) { gapAnswer.add(questionItemObject.getContent()); } copy.setCorrect(String.join(ANSWER_SPLIT, gapAnswer)); } else { copy.setCorrect(question.getCorrect()); } questionAnswerCopyVOList.add(copy); // 填空题需要抹除content(因为是答案) if (QuestionTypeEnum.GapFilling.getCode().equals(doQuestionVO.getQuestionType())) { question.getItems().stream().forEach(option -> { option.setContent(""); }); } doQuestionVO.setQuestionItemList(question.getItems()); doQuestionVO.setId(question.getId()); doQuestionVO.setOriginalFile(question.getOriginalFile()); doQuestionVO.setAudioFile(question.getAudioFile()); return doQuestionVO; }).collect(Collectors.toList()); if (ExamPaperTypeEnum.RandomOrder.getCode().equals(examPaper.getPaperType())) { // 随序试卷打乱顺序 Collections.shuffle(doQuestionVOS); } vo.setQuestionList(doQuestionVOS); return vo; }).collect(Collectors.toList()); } /** * 根据id查找 * * @param id * @return */ @Override public Result detail(Integer id) { ExamVO vo = baseMapper.getById(id); Assert.notNull(vo, "记录不存在"); return Result.ok().data(vo); } /** * 列表 * * @return */ @Override public Result all() { List entities = baseMapper.selectList(null); List vos = entities.stream() .map(entity -> ExamVO.getVoByEntity(entity, null)) .collect(Collectors.toList()); return Result.ok().data(vos); } /** * 主动提交试卷 * * @param submitData 试卷做题提交数据 * @return */ @Override public Result examSubmit(StartExamVO submitData) { // 校验 Exam exam = examMapper.selectById(submitData.getId()); if (Objects.isNull(exam)) { throw new RuntimeException("该考试不存在"); } // // 判断单选、多选、判断题对错 // List questionIds = new ArrayList<>(24); // submitData.getPaperQuestionList().forEach(item -> { // List ids = item.getQuestionList().stream().map(DoQuestionVO::getId).collect(Collectors.toList()); // questionIds.addAll(ids); // }); // List questionList = questionMapper.getAnswerInfo(questionIds); // Map answerMap = questionList.stream().collect(Collectors.toMap(Question::getId, entity -> entity)); // submitData.getQuestionList().stream().forEach(item -> { // Question question = answerMap.get(item.getId()); // if (Objects.nonNull(question) // && (QuestionTypeEnum.SingleChoice.getCode().equals(question.getQuestionType()) // || QuestionTypeEnum.MultipleChoice.getCode().equals(question.getQuestionType()) // || QuestionTypeEnum.TrueFalse.getCode().equals(question.getQuestionType()) // )) { // String correct = question.getCorrect(); // if (QuestionTypeEnum.MultipleChoice.getCode().equals(question.getQuestionType())) { // // 如果是选择题,那么将答案转为list // List answerList = JSON.parseArray(correct, String.class); // item.setRight(answerList.containsAll(item.getAnswerList())); // } else { // item.setRight(question.getCorrect().equals(item.getAnswer())); // } // } // }); // 阅卷后才往exam_paper_answer保存考试成绩、以及保存到exam_paper_customer_answer // 现在只需要保存到一张临时表 // 该接口是主动提交,所以状态都设置为完成,以便后续老师阅卷 saveTempExam(submitData, ExamSubmitTempStatusEnum.finish); return Result.ok(); } /** * 临时保存试卷 * * @param submitData * @return */ @Override public Result timingSubmit(StartExamVO submitData) { saveTempExam(submitData, ExamSubmitTempStatusEnum.temp); return Result.ok(); } /** * 保存试卷:如果接口是定时保存那么是临时试卷。如果接口是自主提交那么是完成试卷 * * @param submitData 前端传递的试卷数据 * @param status 试卷的状态 */ public void saveTempExam(StartExamVO submitData, ExamSubmitTempStatusEnum status) { ExamSubmitTemp one = new LambdaQueryChainWrapper<>(examSubmitTempMapper) .eq(ExamSubmitTemp::getExamId, submitData.getId()) .eq(ExamSubmitTemp::getUserId, webContext.getCurrentUser().getId()) .one(); if (Objects.nonNull(one)) { if (ExamSubmitTempStatusEnum.finish.equals(one.getStatus())) { return; } Date now = new Date(); one.setExamSubmit(JSON.toJSONString(submitData.getTitleList())); one.setUpdateTime(now); int doTimeInSeconds = (int) (now.getTime() - one.getCreateTime().getTime()) / 1000; one.setDoTime(doTimeInSeconds); one.setStatus(status); examSubmitTempMapper.updateById(one); } else { ExamSubmitTemp examSubmitTemp = new ExamSubmitTemp(); examSubmitTemp.setExamId(submitData.getId()); examSubmitTemp.setDoTime(0); examSubmitTemp.setStatus(status); examSubmitTemp.setUserId(webContext.getCurrentUser().getId()); examSubmitTemp.setExamSubmit(JSON.toJSONString(submitData.getTitleList())); examSubmitTemp.setMarkPaperStatus(ExamSubmitTempStatusEnum.temp); examSubmitTempMapper.insert(examSubmitTemp); } } @Override public Result getMarkResultInfo(Integer id) { Exam exam = baseMapper.selectById(id); if (Objects.isNull(exam)) { throw new RuntimeException("该考试不存在"); } ExamPaper examPaper = examPaperMapper.selectById(exam.getExamPaperId()); if (Objects.isNull(examPaper)) { throw new RuntimeException("考试试卷不存在"); } List examSubmitTempList = new LambdaQueryChainWrapper<>(examSubmitTempMapper) .eq(ExamSubmitTemp::getDeleted, 0) .eq(ExamSubmitTemp::getExamId, id) .list(); // 参考人数 Integer joinExamNum = examSubmitTempList.size(); // 参考但未完成提交人数 Integer joinButNotFinishedNum = Math.toIntExact(examSubmitTempList.stream().filter(item -> ExamSubmitTempStatusEnum.temp.equals(item.getStatus())).count()); List studentExamList = classesUserMapper.getClassesUserList(exam.getClassesId()); // 应考人数 Integer shouldUserNum = studentExamList.size(); for (StudentExamInfoVO studentExamInfoVO : studentExamList) { Integer userId = studentExamInfoVO.getUserId(); Optional first = examSubmitTempList.stream().filter(examSubmitTemp -> examSubmitTemp.getUserId().equals(userId)).findFirst(); if (first.isPresent()) { ExamSubmitTemp examSubmitTemp = first.get(); studentExamInfoVO.setMarkPaperStatus(examSubmitTemp.getMarkPaperStatus()); studentExamInfoVO.setStatus(examSubmitTemp.getStatus()); studentExamInfoVO.setDoTime(examSubmitTemp.getDoTime()); } else { //不存在考试记录 studentExamInfoVO.setStatus(ExamSubmitTempStatusEnum.temp); //根据Score表判断 ExamPaperScore paperScore = examPaperScoreMapper.getByExamIdUserId(exam.getId(), userId); if(paperScore==null) { studentExamInfoVO.setMarkPaperStatus(ExamSubmitTempStatusEnum.temp); }else { studentExamInfoVO.setMarkPaperStatus(ExamSubmitTempStatusEnum.finish); } studentExamInfoVO.setDoTime(0); } } MarkPaperVO markPaperVO = new MarkPaperVO(); markPaperVO.setExamName(exam.getExamName()); markPaperVO.setExamId(exam.getId()); markPaperVO.setJoinButNotFinishNum(joinButNotFinishedNum); markPaperVO.setShouldJoinNum(shouldUserNum); markPaperVO.setStudentExamInfoVOList(studentExamList); markPaperVO.setMissJoinNum(shouldUserNum - joinExamNum); markPaperVO.setJoinNum(joinExamNum); markPaperVO.setExamPaperName(examPaper.getName()); markPaperVO.setSuggestTime(examPaper.getSuggestTime()); return Result.ok().data(markPaperVO); } @Override public Result getMarkPaperInfo(Integer examId, Integer userId) { User student = userMapper.getUserById(userId); //如果已经阅过卷了,查成绩表 Result paperMarkVO1 = checkHasJudge(examId, student); if (paperMarkVO1 != null) return paperMarkVO1; ExamVO exam = examMapper.getById(examId); //学生答题表 ExamSubmitTemp userExam = new LambdaQueryChainWrapper<>(examSubmitTempMapper) .eq(ExamSubmitTemp::getExamId, examId) .eq(ExamSubmitTemp::getUserId, userId) .one(); if (Objects.isNull(userExam)) { //缺考,学生没有做题信息 ExamPaperMarkVO paperMarkVO = createVO(null, exam, student); return Result.ok(paperMarkVO); } //封装阅卷基本数据 ExamPaperMarkVO paperMarkVO = createVO(userExam, exam, student); List answerList = JSONArray.parseArray(userExam.getQuestionAnswerCopy(), QuestionAnswerCopyVO.class); //补充题目答案、解析 addAnswer(paperMarkVO, answerList); //阅卷,客观题打分 Result InnerError = markPaper(paperMarkVO); if (InnerError != null) return InnerError; return Result.ok(paperMarkVO); } //补充题目答案、解析 private void addAnswer(ExamPaperMarkVO paperMarkVO, List answerList) { List titleItems = paperMarkVO.getTitleItems(); for (PaperFixQuestionVO titleItem : titleItems) { for (DoQuestionVO doQuestionVO : titleItem.getQuestionList()) { Integer questionId = doQuestionVO.getId(); if(questionId!=null) { Optional first = answerList.stream().filter(answer -> questionId.equals(answer.getId())).findFirst(); if (first.isPresent()) { QuestionAnswerCopyVO answerCopyVO = first.get(); doQuestionVO.setQuestionAnswer(answerCopyVO.getCorrect()); doQuestionVO.setAnalyze(answerCopyVO.getAnalyze()); doQuestionVO.setDifficult(answerCopyVO.getDifficult()); } } } } } //检查是否阅卷 private Result checkHasJudge(Integer examId, User student) { ExamPaperScore examPaperScore = examPaperScoreMapper.getByExamIdUserId(examId, student.getId()); if (examPaperScore != null) { ExamPaperMarkVO paperMarkVO = new ExamPaperMarkVO(); BeanUtils.copyProperties(examPaperScore, paperMarkVO); paperMarkVO.setUserName(student.getRealName()); paperMarkVO.setTotalScore(examPaperScore.getTotalScore() + ""); paperMarkVO.setScore(examPaperScore.getScore() + ""); if (!StringUtils.isEmpty(examPaperScore.getPaperContent())) { List paperFixQuestionVOS = JSONArray.parseArray(examPaperScore.getPaperContent(), PaperFixQuestionVO.class); paperMarkVO.setTitleItems(paperFixQuestionVOS); } if (!StringUtils.isEmpty(examPaperScore.getNavbar())) { paperMarkVO.setNavbar(JSONArray.parseArray(examPaperScore.getNavbar(), ExamPaperMarkNavbarVO.class)); } return Result.ok(paperMarkVO); } return null; } //阅卷 private Result markPaper(ExamPaperMarkVO paperMarkVO) { List titleItems = paperMarkVO.getTitleItems(); //初始化题目序号 Integer num = 1; BigDecimal score = BigDecimal.ZERO; //前端导航数组 List navbar = new ArrayList<>(); //过滤掉题目为空的题目类型 titleItems = titleItems.stream().filter(paperFixQuestionVO -> !CollectionUtils.isEmpty(paperFixQuestionVO.getQuestionList())).collect(Collectors.toList()); for (PaperFixQuestionVO titleItem : titleItems) { for (DoQuestionVO doQuestionVO : titleItem.getQuestionList()) { //准备题目序号供前端跳转使用 ExamPaperMarkNavbarVO navbarVO = new ExamPaperMarkNavbarVO(); //获取试卷类型 Integer questionType = doQuestionVO.getQuestionType(); /* 如果是简答、计算、分析,不设置right只设置题目序号 */ if (QuestionTypeEnum.ShortAnswer.getCode().equals(questionType) || QuestionTypeEnum.Calculate.getCode().equals(questionType) || QuestionTypeEnum.Analysis.getCode().equals(questionType)) { navbarVO.setItemOrder(num); doQuestionVO.setItemOrder(num); } /* 如果是单选、语音、判断(判断答案是A、B) */ else if (QuestionTypeEnum.SingleChoice.getCode().equals(questionType) || QuestionTypeEnum.Audio.getCode().equals(questionType) || QuestionTypeEnum.TrueFalse.getCode().equals(questionType)) { navbarVO.setItemOrder(num); doQuestionVO.setItemOrder(num); if (StringUtils.isEmpty(doQuestionVO.getQuestionAnswer())) { return Result.fail(SystemCode.InnerError.getCode(), "题目id为:" + doQuestionVO.getId() + "的题目缺少答案,请先完善"); } score = trueOrFalse(score, doQuestionVO, navbarVO, doQuestionVO.getQuestionAnswer().equals(doQuestionVO.getAnswer())); } /* 如果是多选 */ else if (QuestionTypeEnum.MultipleChoice.getCode().equals(questionType)) { navbarVO.setItemOrder(num); doQuestionVO.setItemOrder(num); if (StringUtils.isEmpty(doQuestionVO.getQuestionAnswer())) { return Result.fail(SystemCode.InnerError.getCode(), "题目id为:" + doQuestionVO.getId() + "的题目缺少答案,请先完善"); } //学生答案 List answerList = doQuestionVO.getAnswerList(); String questionAnswer = doQuestionVO.getQuestionAnswer(); //题目答案 List questionAnswerList = Arrays.asList(questionAnswer.split(",")); //学生答案为空,判断为错 if (CollectionUtils.isEmpty(answerList)) { score = trueOrFalse(score, doQuestionVO, navbarVO, false); num++; navbar.add(navbarVO); continue; } //答案数量,不需要考虑顺序 int answerCount = answerList.size(); Set set1 = new HashSet<>(answerList); Set set2 = new HashSet<>(questionAnswerList); //答案完全一致,满分 if (set1.equals(set2)) { score = trueOrFalse(score, doQuestionVO, navbarVO, true); num++; navbar.add(navbarVO); continue; } if (paperMarkVO.getDeductType() == null) { return Result.fail(SystemCode.InnerError.getCode(), "试卷没有配置多选得分类型,请联系管理员"); } //如果多选得分类型为 答错不得分 if (Integer.valueOf(DeductTypeEnum.AllCorrect.getCode()).equals(paperMarkVO.getDeductType())) { score = trueOrFalse(score, doQuestionVO, navbarVO, false); } //如果多选得分类型为 漏选得固定分值,包含错误选项不得分 else if (Integer.valueOf(DeductTypeEnum.PartCorrect.getCode()).equals(paperMarkVO.getDeductType())) { //学生答案移除所有正确答案,如果还有元素说明包含错误选项 answerList.removeAll(questionAnswerList); if (!CollectionUtils.isEmpty(answerList)) { score = trueOrFalse(score, doQuestionVO, navbarVO, false); } else { navbarVO.setRight(false); doQuestionVO.setRight(false); //漏选得固定分 doQuestionVO.setScore(paperMarkVO.getDeductScore()); score = score.add(doQuestionVO.getScore()); } } //如果多选得分类型为 每对一题得相应分值,包含错误选项不得分 else if (Integer.valueOf(DeductTypeEnum.EachCorrect.getCode()).equals(paperMarkVO.getDeductType())) { //学生答案移除所有正确答案,如果还有元素说明包含错误选项 answerList.removeAll(questionAnswerList); if (!CollectionUtils.isEmpty(answerList)) { score = trueOrFalse(score, doQuestionVO, navbarVO, false); } else { navbarVO.setRight(false); doQuestionVO.setRight(false); //漏选得分 doQuestionVO.setScore(paperMarkVO.getDeductScore().multiply(new BigDecimal(answerCount))); score = score.add(doQuestionVO.getScore()); } } } /* 如果是填空 */ else if (QuestionTypeEnum.GapFilling.getCode().equals(questionType)) { navbarVO.setItemOrder(num); doQuestionVO.setItemOrder(num); if (StringUtils.isEmpty(doQuestionVO.getQuestionAnswer())) { return Result.fail(SystemCode.InnerError.getCode(), "题目id为:" + doQuestionVO.getId() + "的题目缺少答案,请先完善"); } //学生答案 List answerList = doQuestionVO.getAnswerList(); String questionAnswer = doQuestionVO.getQuestionAnswer(); //题目答案 List questionAnswerList = Arrays.asList(questionAnswer.split(",")); //学生答案为空,判断为错 if (CollectionUtils.isEmpty(answerList)) { score = trueOrFalse(score, doQuestionVO, navbarVO, false); num++; navbar.add(navbarVO); continue; } //总空的数量 int questionAnswerCount = questionAnswerList.size(); //答案完全一致,满分 if (answerList.equals(questionAnswerList)) { score = trueOrFalse(score, doQuestionVO, navbarVO, true); } else { navbarVO.setRight(false); doQuestionVO.setRight(false); //做对的数量,需要按顺序比较 int count = 0; for (int i = 0; i < answerList.size(); i++) { if (answerList.get(i).equals(questionAnswerList.get(i))) { count++; } } //这个题的总分 BigDecimal questionScore = doQuestionVO.getQuestionScore(); //每个空的分数 BigDecimal scoreEach = questionScore.divide(new BigDecimal(questionAnswerCount), 1, RoundingMode.DOWN); //填空得分 BigDecimal gapScore = scoreEach.multiply(new BigDecimal(count)); doQuestionVO.setScore(gapScore); score = score.add(doQuestionVO.getScore()); } } num++; navbar.add(navbarVO); } } paperMarkVO.setTitleItems(titleItems); paperMarkVO.setNavbar(navbar); paperMarkVO.setScore(score + ""); return null; } //设置全对或全错 private BigDecimal trueOrFalse(BigDecimal score, DoQuestionVO doQuestionVO, ExamPaperMarkNavbarVO orderVO, Boolean isCorrect) { if (isCorrect) { //正确 orderVO.setRight(isCorrect); doQuestionVO.setRight(isCorrect); doQuestionVO.setScore(doQuestionVO.getQuestionScore()); score = score.add(doQuestionVO.getQuestionScore()); } else { //错误 orderVO.setRight(isCorrect); doQuestionVO.setRight(isCorrect); doQuestionVO.setScore(BigDecimal.ZERO); } return score; } //封装阅卷返回数据 private ExamPaperMarkVO createVO(ExamSubmitTemp userExam, ExamVO exam, User student) { ExamPaperMarkVO paperMarkVO = new ExamPaperMarkVO(); if (userExam != null) { BeanUtils.copyProperties(userExam, paperMarkVO); paperMarkVO.setSubmitTime(userExam.getUpdateTime()); paperMarkVO.setTitleItems(JSON.parseArray(userExam.getExamSubmit(), PaperFixQuestionVO.class)); } else { //缺考,学生没有做题信息 paperMarkVO.setExamId(exam.getId()); paperMarkVO.setUserId(student.getId()); paperMarkVO.setScore(BigDecimal.ZERO + ""); paperMarkVO.setDoTime(0); } paperMarkVO.setUserName(student.getRealName()); paperMarkVO.setExamName(exam.getExamName()); paperMarkVO.setPaperId(exam.getExamPaperId()); paperMarkVO.setPaperType(exam.getExamPaperType()); ExamPaper examPaper = examPaperMapper.selectById(exam.getExamPaperId()); paperMarkVO.setTotalScore(examPaper.getScore() + ""); paperMarkVO.setDeductType(examPaper.getDeductType()); paperMarkVO.setDeductScore(examPaper.getDeductTypeScore()); return paperMarkVO; } //提交批改 @Override @Transactional(rollbackFor = Exception.class) public Result submitMarkPaper(ExamPaperMarkVO examPaperMark) { Integer userId = webContext.getCurrentUser().getId(); //插入exam_paper_answer(成绩表) ExamPaperScore examPaperScore = new ExamPaperScore(); BeanUtils.copyProperties(examPaperMark, examPaperScore); examPaperScore.setScore(new BigDecimal(examPaperMark.getScore())); examPaperScore.setTotalScore(new BigDecimal(examPaperMark.getTotalScore())); examPaperScore.setJudgeUser(userId); examPaperScore.setJudgeTime(new Date()); if (!StringUtils.isEmpty(examPaperMark.getTitleItems())) { examPaperScore.setPaperContent(JSON.toJSONString(examPaperMark.getTitleItems())); } if (!StringUtils.isEmpty(examPaperMark.getNavbar())) { examPaperScore.setNavbar(JSON.toJSONString(examPaperMark.getNavbar())); } long questionCorrect = 0; long questionCount = 0; if (!CollectionUtils.isEmpty(examPaperMark.getNavbar())) { examPaperScore.setStatus(ExamScoreConstant.PRESENT); questionCorrect = examPaperMark.getNavbar().stream().filter(vo -> vo.getRight() != null && vo.getRight()).count(); questionCount = examPaperMark.getNavbar().size(); } else { //缺考查试卷配置 Integer paperId = examPaperMark.getPaperId(); ExamPaper examPaper = examPaperMapper.selectById(paperId); questionCount = examPaper.getNum(); examPaperScore.setStatus(ExamScoreConstant.ABSENT); } examPaperScore.setQuestionCorrect(Integer.valueOf(questionCorrect + "")); examPaperScore.setQuestionCount(Integer.valueOf(questionCount + "")); //找之前有无批改记录 ExamPaperScore score = examPaperScoreMapper.getByExamIdUserId(examPaperMark.getExamId(), examPaperMark.getUserId()); if (score != null) { examPaperScore.setId(score.getId()); examPaperScoreMapper.updateById(examPaperScore); } else { examPaperScoreMapper.insert(examPaperScore); //修改考试里试卷状态为已阅卷 ExamSubmitTemp userExam = new LambdaQueryChainWrapper<>(examSubmitTempMapper) .eq(ExamSubmitTemp::getExamId, examPaperMark.getExamId()) .eq(ExamSubmitTemp::getUserId, examPaperMark.getUserId()) .one(); if (userExam != null) { userExam.setMarkPaperStatus(ExamSubmitTempStatusEnum.finish); examSubmitTempMapper.updateById(userExam); } } return Result.ok(); } @Override public Result monitorList(ExamQuery query) { IPage page = PageUtil.getPage(query, ExamSubmitTempVO.class); IPage vo = examSubmitTempMapper.monitorList(page, query); return Result.ok(vo); } @Override public Result addTime(AddTimeForm form) { if (!websocketServer.checkUserOnline(form.getUserId())) { throw new RuntimeException("该学员不在线,无法执行该操作"); } WebsocketDataVO websocket = new WebsocketDataVO(); websocket.setCommend(WebsocketCommendEnum.DELAYED.getCommand()); BigDecimal sed = BigDecimal.valueOf(60).multiply(form.getAddTimeM()); form.setAddTimeM(sed); websocket.setData(form); // 发送websocket消息 websocketServer.sendOneMessage(form.getUserId(), JSON.toJSONString(websocket)); return Result.ok("操作成功"); } @Override public Result forceSubmit(ForceSubmitForm form) { if (!websocketServer.checkUserOnline(form.getUserId())) { throw new RuntimeException("该学员不在线,无法执行该操作"); } WebsocketDataVO websocket = new WebsocketDataVO(); websocket.setCommend(WebsocketCommendEnum.FORCE_SUBMIT.getCommand()); websocket.setData(form); // 发送websocket消息 websocketServer.sendOneMessage(form.getUserId(), JSON.toJSONString(websocket)); return Result.ok("操作成功"); } /** * 作废 * * @param id * @return {@link Result } * @author */ @Override public Result cancel(Integer id) { new LambdaUpdateChainWrapper<>(examMapper) .eq(Exam::getId, id) .set(Exam::getStatus, ExamStatusEnum.CANCEL) .update(); return Result.ok("作废成功"); } @Override public Result recover(Integer id) { // 先查询当前考试记录的详细信息 Exam examInfo = new LambdaQueryChainWrapper<>(examMapper) .eq(Exam::getId, id) .one(); // 确定恢复后当前考试记录的状态 Date currentTime = new Date(); Date startTime = examInfo.getStartTime(); Date endTime = examInfo.getEndTime(); ExamStatusEnum statusByTime = ExamStatusEnum.getStatusByTime(startTime, endTime, currentTime); examInfo.setStatus(statusByTime); // 修改当前的考试状态 new LambdaUpdateChainWrapper<>(examMapper) .eq(Exam::getId, id) .set(Exam::getStatus, examInfo.getStatus()) .update(); // 还原班级的考试信息 return Result.ok("考试记录已经恢复正常"); } }