Bladeren bron

按考生ID重算成绩分数

deason 1 jaar geleden
bovenliggende
commit
9a9cdbc01e

+ 9 - 5
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamScoreController.java

@@ -6,10 +6,12 @@ import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
 import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.FixExamScoreService;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreQuery;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ObjectiveScoreInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ReFixScoreReq;
 import cn.com.qmth.examcloud.core.oe.admin.service.util.AsyncExportConcurrentUtil;
 import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
 import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
@@ -42,6 +44,9 @@ public class ExamScoreController extends ControllerSupport {
     @Autowired
     private ExamScoreService examScoreService;
 
+    @Autowired
+    private FixExamScoreService fixExamScoreService;
+
     @DataRule(type = { DataRuleType.COURSE, DataRuleType.ORG })
     @PostMapping("/statistic/list")
     @ApiOperation(value = "分页查询成绩明细列表")
@@ -87,11 +92,10 @@ public class ExamScoreController extends ControllerSupport {
         return examScoreService.queryObjectiveScoreList(examStudentId);
     }
 
-    @ApiOperation(value = "按考生ID重新计算成绩分数(用于实施小工具)")
-    @GetMapping("/updateCorrectAnswerAndReFixScore")
-    public void reFixScoreByExamStudentId(@RequestParam Long examId, @RequestParam Long courseId,
-            @RequestParam Long examStudentId) {
-        examScoreService.reFixScoreByExamStudentId(examId, courseId, examStudentId);
+    @ApiOperation(value = "按考生ID重算成绩分数(用于实施小工具)")
+    @PostMapping("/reFixScore")
+    public void reFixScoreByExamStudentId(@RequestBody ReFixScoreReq req) {
+        fixExamScoreService.reFixScoreByExamStudentId(req);
     }
 
 }

+ 5 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordDataRepo.java

@@ -118,4 +118,9 @@ public interface ExamRecordDataRepo extends JpaRepository<ExamRecordDataEntity,
     @Query("update ExamRecordDataEntity set examRecordStatus = :examRecordStatus, endTime = :endTime where id = :id")
     int updateExamRecordStatusById(@Param("id") long id, @Param("examRecordStatus") ExamRecordStatus examRecordStatus, @Param("endTime") Date endTime);
 
+    @Transactional
+    @Modifying
+    @Query("update ExamRecordDataEntity set paperScore = :paperScore where id = :id")
+    int updateExamRecordPaperScoreById(@Param("id") long id, @Param("paperScore") double paperScore);
+
 }

+ 0 - 6
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamScoreService.java

@@ -56,10 +56,4 @@ public interface ExamScoreService {
 
     List<ExamScoreInfo> exportExamScoreListForAsync(UserDataRules uds, ExamScoreQuery query);
 
-    /**
-     * 按考生ID重新计算成绩分数(用于实施小工具)
-     * 适用场景:修改试题正确答案、试题分值
-     */
-    void reFixScoreByExamStudentId(Long examId, Long courseId, Long examStudentId);
-
 }

+ 12 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/FixExamScoreService.java

@@ -0,0 +1,12 @@
+package cn.com.qmth.examcloud.core.oe.admin.service;
+
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ReFixScoreReq;
+
+public interface FixExamScoreService {
+
+    /**
+     * 按考生ID重算成绩分数(用于实施小工具,适用场景:考后修改试题正确答案、试题分值)
+     */
+    void reFixScoreByExamStudentId(ReFixScoreReq req);
+
+}

+ 87 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examscore/ReFixScoreReq.java

@@ -0,0 +1,87 @@
+package cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class ReFixScoreReq {
+
+    private Long examId;
+
+    private Long courseId;
+
+    @ApiModelProperty(value = " 课程代码", hidden = true)
+    private String courseCode;
+
+    private Long examStudentId;
+
+    /**
+     * 是否更新客观题答案
+     */
+    private Boolean updateObjectiveAnswer;
+
+    /**
+     * 是否更新客观题分值
+     */
+    private Boolean updateObjectiveScore;
+
+    /**
+     * 是否更新主观题分值
+     */
+    private Boolean updateSubjectiveScore;
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public Long getExamStudentId() {
+        return examStudentId;
+    }
+
+    public void setExamStudentId(Long examStudentId) {
+        this.examStudentId = examStudentId;
+    }
+
+    public Boolean getUpdateObjectiveAnswer() {
+        return updateObjectiveAnswer;
+    }
+
+    public void setUpdateObjectiveAnswer(Boolean updateObjectiveAnswer) {
+        this.updateObjectiveAnswer = updateObjectiveAnswer;
+    }
+
+    public Boolean getUpdateObjectiveScore() {
+        return updateObjectiveScore;
+    }
+
+    public void setUpdateObjectiveScore(Boolean updateObjectiveScore) {
+        this.updateObjectiveScore = updateObjectiveScore;
+    }
+
+    public Boolean getUpdateSubjectiveScore() {
+        return updateSubjectiveScore;
+    }
+
+    public void setUpdateSubjectiveScore(Boolean updateSubjectiveScore) {
+        this.updateSubjectiveScore = updateSubjectiveScore;
+    }
+
+}

+ 11 - 161
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreServiceImpl.java

@@ -7,17 +7,17 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
-import cn.com.qmth.examcloud.api.commons.enums.ExamType;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.DateUtil;
 import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SpecUtils;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.CommonUtil;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
-import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordForMarkingRepo;
-import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordQuestionsRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
-import cn.com.qmth.examcloud.core.oe.admin.dao.entity.*;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentFinalScoreEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.CourseLevel;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
 import cn.com.qmth.examcloud.core.oe.admin.service.*;
@@ -28,9 +28,11 @@ import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreQuery
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ObjectiveScoreInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.util.BatchGetDataUtil;
-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.*;
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamStageCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
 import cn.com.qmth.examcloud.support.examing.ExamBoss;
 import cn.com.qmth.examcloud.support.examing.ExamRecordData;
@@ -53,7 +55,9 @@ import org.springframework.util.CollectionUtils;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.text.DecimalFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 /**
@@ -76,15 +80,6 @@ public class ExamScoreServiceImpl implements ExamScoreService {
     @Autowired
     private ExamRecordDataRepo examRecordDataRepo;
 
-    @Autowired
-    private ExamRecordQuestionsRepo examRecordQuestionsRepo;
-
-    @Autowired
-    private ExamRecordForMarkingRepo examRecordForMarkingRepo;
-
-    @Autowired
-    private ExamRecordQuestionsService examRecordQuestionsService;
-
     @Autowired
     private ExamStudentService examStudentService;
 
@@ -779,149 +774,4 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         return false;
     }
 
-    /**
-     * 按考生ID重新计算成绩分数(用于实施小工具)
-     * 适用场景:修改试题正确答案、试题分值
-     */
-    @Override
-    public void reFixScoreByExamStudentId(Long examId, Long courseId, Long examStudentId) {
-        // 单场考试可能存在多条考试记录
-        List<ExamRecordDataEntity> examRecordDataList = examRecordDataRepo.findByExamIdAndCourseIdAndExamStudentId(
-                examId, courseId, examStudentId);
-        if (CollectionUtils.isEmpty(examRecordDataList)) {
-            log.warn("ReFixScore examRecordDataList is empty, examStudentId = {}", examStudentId);
-            return;
-        }
-
-        // Map<试题ID, 正确答案列表>
-        Map<String, List<String>> questionAnswerMaps = new HashMap<>();
-        boolean needUpdateFinalScore = false;
-
-        for (ExamRecordDataEntity examRecordData : examRecordDataList) {
-            if (ExamType.OFFLINE == examRecordData.getExamType()) {
-                // 离线考试,examRecordQuestions实际为空
-                continue;
-            }
-
-            ExamRecordQuestionsEntity examRecordQuestion = examRecordQuestionsService.getExamRecordQuestionsAndFixExamRecordDataIfNecessary(
-                    examRecordData);
-            if (examRecordQuestion == null) {
-                log.warn("ReFixScore examRecordQuestions is null, examRecordDataId = {}", examRecordData.getId());
-                continue;
-            }
-
-            // 所有客观题的试题ID列表
-            List<ExamQuestionEntity> examQuestions = examRecordQuestion.getExamQuestionEntities();
-            Set<String> objectiveQuestionIds = examQuestions.stream()
-                    .filter(e -> QuestionType.isObjective(e.getQuestionType())).map(ExamQuestionEntity::getQuestionId)
-                    .collect(Collectors.toSet());
-
-            double objectiveScore = 0d; // 客观题得分
-            int totalObjective = 0; // 客观题总数
-            int totalRightObjective = 0; // 客观题作答正确数
-            boolean needUpdateExamRecordQuestions = false;
-
-            for (String curQuestionId : objectiveQuestionIds) {
-                // 按题号由小到大提取相同题ID的小题列表(注:套题的子题ID相同)
-                List<ExamQuestionEntity> questionUnits = examQuestions.stream()
-                        .filter(e -> e.getQuestionId().equals(curQuestionId))
-                        .sorted(Comparator.comparingInt(ExamQuestionEntity::getOrder)).collect(Collectors.toList());
-
-                for (ExamQuestionEntity e : questionUnits) {
-                    if (QuestionType.isObjective(e.getQuestionType())) {
-                        totalObjective++;
-                    }
-                }
-
-                // 获取当前题的正确答案
-                List<String> rightAnswers;
-                if (questionAnswerMaps.containsKey(curQuestionId)) {
-                    rightAnswers = questionAnswerMaps.get(curQuestionId);
-                } else {
-                    QuestionAnswerCacheBean questionAnswer = CacheHelper.getQuestionAnswer(curQuestionId);
-                    rightAnswers = questionAnswer.getRightAnswers();
-                    questionAnswerMaps.put(curQuestionId, rightAnswers);
-                }
-
-                if (CollectionUtils.isEmpty(rightAnswers)) {
-                    log.warn("ReFixScore rightAnswers is empty, questionId = {}", curQuestionId);
-                    continue;
-                }
-
-                if (questionUnits.size() != rightAnswers.size()) {
-                    log.warn("ReFixScore rightAnswers incorrect, questionId = {}", curQuestionId);
-                    continue;
-                }
-
-                for (int i = 0; i < questionUnits.size(); i++) {
-                    ExamQuestionEntity curQuestion = questionUnits.get(i);
-                    if (!QuestionType.isObjective(curQuestion.getQuestionType())) {
-                        // 跳过套题内主观题
-                        continue;
-                    }
-
-                    // 答案不一致,则更新
-                    String rightAnswer = rightAnswers.get(i);
-                    if (rightAnswer != null && !rightAnswer.equals(curQuestion.getCorrectAnswer())) {
-                        curQuestion.setCorrectAnswer(rightAnswer);
-                        needUpdateExamRecordQuestions = true;
-                    }
-
-                    // 计算得分
-                    String correctAnswer = curQuestion.getCorrectAnswer();
-                    String studentAnswer = curQuestion.getStudentAnswer();
-                    // if (StringUtils.isNotEmpty(studentAnswer) && QuestionOptionHelper.isEqualAnswer(correctAnswer, studentAnswer)) {
-                    if (StringUtils.isNotEmpty(studentAnswer) && studentAnswer.equals(correctAnswer)) {
-                        Double questionScore = curQuestion.getQuestionScore();
-                        if (questionScore == null) {
-                            log.warn("[calcExamScore] questionScore is null, examRecordDataId:{} questionId:{}",
-                                    examRecordData.getId(), curQuestionId);
-                            questionScore = 0d;
-                        }
-
-                        objectiveScore += questionScore;
-                        totalRightObjective++;
-                    }
-                }
-            }
-
-            if (needUpdateExamRecordQuestions) {
-                needUpdateFinalScore = true;
-
-                examRecordQuestionsRepo.save(examRecordQuestion);
-
-                ExamScoreEntity examScore = examScoreRepo.findByExamRecordDataId(examRecordData.getId());
-                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(
-                        examRecordData.getId());
-                if (examRecordForMarking != null) {
-                    examRecordForMarking.setObjectiveScore(objectiveScore);
-                    examRecordForMarkingRepo.save(examRecordForMarking);
-                }
-
-                log.info("ReFixScore examRecordQuestions examRecordDataId = {}", examRecordData.getId());
-            }
-        }
-
-        if (needUpdateFinalScore) {
-            examStudentFinalScoreService.calcAndSaveFinalScore(examStudentId);
-            log.info("ReFixScore finalScore examStudentId = {}", examStudentId);
-        }
-    }
-
 }

+ 344 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/FixExamScoreServiceImpl.java

@@ -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);
+                }
+            }
+        }
+    }
+
+}