|
@@ -0,0 +1,344 @@
|
|
|
+package cn.com.qmth.examcloud.core.oe.admin.service.impl;
|
|
|
+
|
|
|
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.dao.*;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.*;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordQuestionsService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentFinalScoreService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.service.FixExamScoreService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ReFixScoreReq;
|
|
|
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
|
|
|
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionGroup;
|
|
|
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionStructureWrapper;
|
|
|
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionUnitWrapper;
|
|
|
+import cn.com.qmth.examcloud.question.commons.core.question.QuestionType;
|
|
|
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
|
|
|
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
|
|
|
+import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigPaperCacheBean;
|
|
|
+import cn.com.qmth.examcloud.support.cache.bean.QuestionAnswerCacheBean;
|
|
|
+import org.apache.commons.collections.CollectionUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.text.DecimalFormat;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class FixExamScoreServiceImpl implements FixExamScoreService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(FixExamScoreServiceImpl.class);
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordDataRepo examRecordDataRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordPaperStructRepo examRecordPaperStructRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordQuestionsRepo examRecordQuestionsRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamScoreRepo examScoreRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordForMarkingRepo examRecordForMarkingRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordQuestionsService examRecordQuestionsService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamStudentFinalScoreService examStudentFinalScoreService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按考生ID重算成绩分数(用于实施小工具,适用场景:考后修改试题正确答案、试题分值)
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void reFixScoreByExamStudentId(ReFixScoreReq req) {
|
|
|
+ if (req.getExamId() == null || req.getCourseId() == null || req.getExamStudentId() == null) {
|
|
|
+ log.warn("【重算成绩】跳过,请求参数值为空!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (req.getUpdateObjectiveAnswer() == null) {
|
|
|
+ req.setUpdateObjectiveAnswer(false);
|
|
|
+ }
|
|
|
+ if (req.getUpdateObjectiveScore() == null) {
|
|
|
+ req.setUpdateObjectiveScore(false);
|
|
|
+ }
|
|
|
+ if (req.getUpdateSubjectiveScore() == null) {
|
|
|
+ req.setUpdateSubjectiveScore(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 单场考试可能存在多条考试记录
|
|
|
+ List<ExamRecordDataEntity> examRecordDataList = examRecordDataRepo.findByExamIdAndCourseIdAndExamStudentId(
|
|
|
+ req.getExamId(), req.getCourseId(), req.getExamStudentId());
|
|
|
+ if (CollectionUtils.isEmpty(examRecordDataList)) {
|
|
|
+ log.warn("【重算成绩】跳过,考试记录为空! examStudentId:{}", req.getExamStudentId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ CourseCacheBean courseCache = CacheHelper.getCourse(req.getCourseId());
|
|
|
+ req.setCourseCode(courseCache.getCode());
|
|
|
+
|
|
|
+ // 是否需要更新最终成绩
|
|
|
+ boolean needUpdateFinalScore = false;
|
|
|
+
|
|
|
+ // 逐个按考试记录分别修正成绩
|
|
|
+ for (ExamRecordDataEntity examRecordData : examRecordDataList) {
|
|
|
+ boolean hasChanged = this.reFixScoreByExamRecordData(req, examRecordData);
|
|
|
+ if (hasChanged) {
|
|
|
+ needUpdateFinalScore = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (needUpdateFinalScore) {
|
|
|
+ examStudentFinalScoreService.calcAndSaveFinalScore(req.getExamStudentId());
|
|
|
+ log.warn("【重算成绩】修改最终成绩! examStudentId:{}", req.getExamStudentId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean reFixScoreByExamRecordData(ReFixScoreReq req, ExamRecordDataEntity examRecordData) {
|
|
|
+ if (ExamType.OFFLINE == examRecordData.getExamType()) {
|
|
|
+ // 注:离线考试的examRecordQuestions实际为空
|
|
|
+ log.warn("【重算成绩】跳过,离线考试记录! examStudentId:{}", examRecordData.getExamStudentId());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (examRecordData.getRandomPaper()) {
|
|
|
+ // 注:千人千卷调卷模式,试题的分值只在千卷试卷结构上体现,考生随机生成的试卷试题 不能像成卷调卷模式一样 准确找到试题所在原卷中的试题分值
|
|
|
+ log.warn("【重算成绩】跳过,千人千卷调卷模式暂不支持修改! examStudentId:{} examRecordDataId:{}",
|
|
|
+ examRecordData.getExamStudentId(), examRecordData.getId());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ExamRecordQuestionsEntity examRecordQuestion = examRecordQuestionsService.getExamRecordQuestionsAndFixExamRecordDataIfNecessary(
|
|
|
+ examRecordData);
|
|
|
+ if (examRecordQuestion == null || examRecordQuestion.getExamQuestionEntities() == null) {
|
|
|
+ log.warn("【重算成绩】跳过,试题作答记录为空! examStudentId:{} examRecordDataId:{}",
|
|
|
+ examRecordData.getExamStudentId(), examRecordData.getId());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ List<ExamQuestionEntity> examQuestions = examRecordQuestion.getExamQuestionEntities();
|
|
|
+
|
|
|
+ log.info("【重算成绩】开始!examRecordDataId:{} paperStructId:{} basePaperId:{} paperScore:{}",
|
|
|
+ examRecordData.getId(), examRecordData.getPaperStructId(), examRecordData.getBasePaperId(),
|
|
|
+ examRecordData.getPaperScore());
|
|
|
+
|
|
|
+ // 需要更新试题分值,则优先执行
|
|
|
+ if (req.getUpdateObjectiveScore() || req.getUpdateSubjectiveScore()) {
|
|
|
+ this.changeQuestionScore(req, examRecordData, examQuestions);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新试题正确答案
|
|
|
+ if (req.getUpdateObjectiveAnswer()) {
|
|
|
+ this.changeQuestionRightAnswer(examQuestions);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存考生作答记录
|
|
|
+ examRecordQuestionsRepo.save(examRecordQuestion);
|
|
|
+
|
|
|
+ // 计算成绩
|
|
|
+ this.calcExamScore(examRecordData.getId(), examQuestions);
|
|
|
+
|
|
|
+ log.info("【重算成绩】结束!examRecordDataId:{} newPaperScore:{}", examRecordData.getId(),
|
|
|
+ examRecordData.getPaperScore());
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void calcExamScore(Long examRecordDataId, List<ExamQuestionEntity> examQuestions) {
|
|
|
+ int totalObjective = 0; // 客观题总数
|
|
|
+ int totalRightObjective = 0; // 客观题作答正确数
|
|
|
+ double objectiveScore = 0d; // 客观题得分
|
|
|
+
|
|
|
+ for (ExamQuestionEntity curQuestion : examQuestions) {
|
|
|
+ if (!QuestionType.isObjective(curQuestion.getQuestionType())) {
|
|
|
+ // 跳过主观题
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ totalObjective++;
|
|
|
+
|
|
|
+ // 计算得分
|
|
|
+ String correctAnswer = curQuestion.getCorrectAnswer();
|
|
|
+ String studentAnswer = curQuestion.getStudentAnswer();
|
|
|
+ // if (StringUtils.isNotEmpty(studentAnswer) && QuestionOptionHelper.isEqualAnswer(correctAnswer, studentAnswer)) {
|
|
|
+ if (StringUtils.isNotEmpty(studentAnswer) && studentAnswer.equals(correctAnswer)) {
|
|
|
+ if (curQuestion.getQuestionScore() == null) {
|
|
|
+ curQuestion.setQuestionScore(0d);
|
|
|
+ }
|
|
|
+
|
|
|
+ objectiveScore += curQuestion.getQuestionScore();
|
|
|
+ totalRightObjective++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ExamScoreEntity examScore = examScoreRepo.findByExamRecordDataId(examRecordDataId);
|
|
|
+ if (examScore != null) {
|
|
|
+ double subjectiveScore = examScore.getSubjectiveScore() != null ? examScore.getSubjectiveScore() : 0d;
|
|
|
+
|
|
|
+ double objectiveAccuracy = 0d;// 客观题正确率
|
|
|
+ if (totalObjective > 0) {
|
|
|
+ objectiveAccuracy = totalRightObjective * 100d / totalObjective;
|
|
|
+ objectiveAccuracy = Double.parseDouble(new DecimalFormat("#.00").format(objectiveAccuracy));
|
|
|
+ }
|
|
|
+
|
|
|
+ examScore.setObjectiveScore(objectiveScore);
|
|
|
+ examScore.setObjectiveAccuracy(objectiveAccuracy);
|
|
|
+ examScore.setTotalScore(objectiveScore + subjectiveScore);
|
|
|
+ examScoreRepo.save(examScore);
|
|
|
+ }
|
|
|
+
|
|
|
+ ExamRecordForMarkingEntity examRecordForMarking = examRecordForMarkingRepo.findByExamRecordDataId(
|
|
|
+ examRecordDataId);
|
|
|
+ if (examRecordForMarking != null) {
|
|
|
+ examRecordForMarking.setObjectiveScore(objectiveScore);
|
|
|
+ examRecordForMarkingRepo.save(examRecordForMarking);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("【重算成绩】 examRecordDataId:{} objectiveScore:{}", examRecordDataId, objectiveScore);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void changeQuestionScore(ReFixScoreReq req, ExamRecordDataEntity examRecordData,
|
|
|
+ List<ExamQuestionEntity> examQuestions) {
|
|
|
+ // 获取考生考试时实际试卷结构
|
|
|
+ Optional<ExamRecordPaperStructEntity> examRecordPaperStructOptional = examRecordPaperStructRepo.findById(
|
|
|
+ examRecordData.getPaperStructId());
|
|
|
+ if (!examRecordPaperStructOptional.isPresent()) {
|
|
|
+ log.warn("【修改试题分值】考生试卷结构为空! examStudentId:{} examRecordDataId:{} paperStructId:{}",
|
|
|
+ examRecordData.getExamStudentId(), examRecordData.getId(), examRecordData.getPaperStructId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ExamRecordPaperStructEntity examRecordPaperStruct = examRecordPaperStructOptional.get();
|
|
|
+
|
|
|
+ // 获取原卷试卷结构
|
|
|
+ ExtractConfigPaperCacheBean basePaperStruct = CacheHelper.getExtractConfigPaper(req.getExamId(),
|
|
|
+ req.getCourseCode(), examRecordData.getPaperType(), examRecordData.getBasePaperId());
|
|
|
+ if (basePaperStruct == null) {
|
|
|
+ log.warn("【修改试题分值】原卷试卷结构为空! examStudentId:{} examRecordDataId:{} basePaperId:{}",
|
|
|
+ examRecordData.getExamStudentId(), examRecordData.getId(), examRecordData.getBasePaperId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ DefaultPaper baseDefaultPaper = basePaperStruct.getDefaultPaper();
|
|
|
+ // Map<questionId,questionUnits> 原卷试题集合
|
|
|
+ Map<String, List<DefaultQuestionUnitWrapper>> baseQuestionUnitMaps = new HashMap<>();
|
|
|
+ for (DefaultQuestionGroup baseGroup : baseDefaultPaper.getQuestionGroupList()) {
|
|
|
+ for (DefaultQuestionStructureWrapper baseQuestion : baseGroup.getQuestionWrapperList()) {
|
|
|
+ baseQuestionUnitMaps.put(baseQuestion.getQuestionId(), baseQuestion.getQuestionUnitWrapperList());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ double paperScore = 0D;//试卷总分
|
|
|
+
|
|
|
+ // 更新试卷结构的试题分值
|
|
|
+ DefaultPaper examRecordDefaultPaper = examRecordPaperStruct.getDefaultPaper();
|
|
|
+ for (DefaultQuestionGroup group : examRecordDefaultPaper.getQuestionGroupList()) {
|
|
|
+ double groupScore = 0d;//大题总分
|
|
|
+
|
|
|
+ for (DefaultQuestionStructureWrapper question : group.getQuestionWrapperList()) {
|
|
|
+ List<DefaultQuestionUnitWrapper> baseQuestionUnits = baseQuestionUnitMaps.get(question.getQuestionId());
|
|
|
+ if (CollectionUtils.isNotEmpty(baseQuestionUnits)) {
|
|
|
+ double questionScore = 0d;//小题总分;
|
|
|
+ for (int n = 0; n < question.getQuestionUnitWrapperList().size(); n++) {
|
|
|
+ DefaultQuestionUnitWrapper questionUnit = question.getQuestionUnitWrapperList().get(n);
|
|
|
+ DefaultQuestionUnitWrapper baseQuestionUnit = baseQuestionUnits.get(n);
|
|
|
+
|
|
|
+ if (baseQuestionUnit.getQuestionScore() == null) {
|
|
|
+ baseQuestionUnit.setQuestionScore(0d);
|
|
|
+ }
|
|
|
+
|
|
|
+ questionUnit.setQuestionScore(baseQuestionUnit.getQuestionScore());// 修改小题分值
|
|
|
+ questionScore += baseQuestionUnit.getQuestionScore();
|
|
|
+ }
|
|
|
+ question.setQuestionScore(questionScore);// 修改小题分值
|
|
|
+ groupScore += questionScore;
|
|
|
+ } else {
|
|
|
+ groupScore += question.getQuestionScore();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ group.setGroupScore(groupScore);// 修改大题分值
|
|
|
+ paperScore += groupScore;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新作答记录的试题分值
|
|
|
+ Set<String> examQuestionIds = examQuestions.stream().map(ExamQuestionEntity::getQuestionId)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ for (String curExamQuestionId : examQuestionIds) {
|
|
|
+ List<ExamQuestionEntity> curQuestionUnits = examQuestions.stream()
|
|
|
+ .filter(e -> e.getQuestionId().equals(curExamQuestionId))
|
|
|
+ .sorted(Comparator.comparingInt(ExamQuestionEntity::getOrder)).collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<DefaultQuestionUnitWrapper> baseQuestionUnits = baseQuestionUnitMaps.get(curExamQuestionId);
|
|
|
+ if (CollectionUtils.isNotEmpty(baseQuestionUnits)) {
|
|
|
+ for (int n = 0; n < curQuestionUnits.size(); n++) {
|
|
|
+ DefaultQuestionUnitWrapper baseQuestionUnit = baseQuestionUnits.get(n);
|
|
|
+ if (baseQuestionUnit.getQuestionScore() == null) {
|
|
|
+ baseQuestionUnit.setQuestionScore(0d);
|
|
|
+ }
|
|
|
+
|
|
|
+ ExamQuestionEntity questionUnit = curQuestionUnits.get(n);
|
|
|
+ questionUnit.setQuestionScore(baseQuestionUnit.getQuestionScore());// 修改小题分值
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存考生试卷结构
|
|
|
+ examRecordPaperStructRepo.save(examRecordPaperStruct);
|
|
|
+ log.info("【修改试题分值】更新试卷结构成功! examRecordDataId:{} paperStructId:{}", examRecordData.getId(),
|
|
|
+ examRecordPaperStruct.getId());
|
|
|
+
|
|
|
+ if (paperScore != examRecordData.getPaperScore()) {
|
|
|
+ // 更新考试记录的试卷总分
|
|
|
+ examRecordData.setPaperScore(paperScore);
|
|
|
+ examRecordDataRepo.updateExamRecordPaperScoreById(examRecordData.getId(), paperScore);
|
|
|
+ log.info("【修改试题分值】更新考试记录的试卷总分成功! examRecordDataId:{} paperScore:{}",
|
|
|
+ examRecordData.getId(), paperScore);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void changeQuestionRightAnswer(List<ExamQuestionEntity> examQuestions) {
|
|
|
+ // 所有客观题的试题ID列表
|
|
|
+ Set<String> objectiveQuestionIds = examQuestions.stream()
|
|
|
+ .filter(e -> QuestionType.isObjective(e.getQuestionType())).map(ExamQuestionEntity::getQuestionId)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ for (String curExamQuestionId : objectiveQuestionIds) {
|
|
|
+ // 按题号由小到大提取相同题ID的小题列表(注:套题的子题ID相同)
|
|
|
+ List<ExamQuestionEntity> curQuestionUnits = examQuestions.stream()
|
|
|
+ .filter(e -> e.getQuestionId().equals(curExamQuestionId))
|
|
|
+ .sorted(Comparator.comparingInt(ExamQuestionEntity::getOrder)).collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 获取当前题的正确答案
|
|
|
+ QuestionAnswerCacheBean questionAnswer = CacheHelper.getQuestionAnswer(curExamQuestionId);
|
|
|
+ List<String> rightAnswers = questionAnswer.getRightAnswers();
|
|
|
+ if (CollectionUtils.isEmpty(rightAnswers)) {
|
|
|
+ log.warn("【修改试题正确答案】跳过,正确答案为空!questionId:{}", curExamQuestionId);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (curQuestionUnits.size() != rightAnswers.size()) {
|
|
|
+ log.warn("【修改试题正确答案】跳过,正确答案集合与小题数量不匹配!questionId:{}", curExamQuestionId);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < curQuestionUnits.size(); i++) {
|
|
|
+ ExamQuestionEntity curQuestion = curQuestionUnits.get(i);
|
|
|
+ if (!QuestionType.isObjective(curQuestion.getQuestionType())) {
|
|
|
+ // 跳过套题内主观题
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 答案不一致,则更新
|
|
|
+ String rightAnswer = rightAnswers.get(i);
|
|
|
+ if (rightAnswer != null && !rightAnswer.equals(curQuestion.getCorrectAnswer())) {
|
|
|
+ curQuestion.setCorrectAnswer(rightAnswer);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|