Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/dev_v3.4.5' into dev_v3.4.5

wangliang 2 mesi fa
parent
commit
6fe48c2dbf

+ 1 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/mapper/MarkTaskMapper.java

@@ -37,6 +37,7 @@ public interface MarkTaskMapper extends BaseMapper<MarkTask> {
                                 @Param("userId") Long userId);
 
     List<MarkTask> findUnMarkedFilterClass(@Param("page") Page<MarkTask> page, @Param("examId") Long examId, @Param("paperNumber") String paperNumber, @Param("userId") Long userId, @Param("questionId") Long questionId, @Param("classNames") List<String> classNames);
+    List<MarkTask> findAiUnMarked(@Param("page") Page<MarkTask> page, @Param("examId") Long examId, @Param("paperNumber") String paperNumber, @Param("questionId") Long questionId);
 
     List<MarkTask> listByExamIdAndPaperNumberAndQuestionIdAndUserIdAndClassName(@Param("examId") Long examId, @Param("paperNumber") String paperNumber, @Param("questionId") Long questionId, @Param("userId") Long userId, @Param("className") String className);
 

+ 110 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/AiMarkService.java

@@ -0,0 +1,110 @@
+package com.qmth.teachcloud.mark.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.qmth.teachcloud.common.entity.MarkQuestion;
+import com.qmth.teachcloud.common.entity.SysUser;
+import com.qmth.teachcloud.mark.dto.mark.manage.Task;
+import com.qmth.teachcloud.mark.dto.mark.mark.MarkSettingDto;
+import com.qmth.teachcloud.mark.dto.mark.mark.MarkStatusDto;
+import com.qmth.teachcloud.mark.dto.mark.mark.SubmitResult;
+import com.qmth.teachcloud.mark.entity.*;
+import com.qmth.teachcloud.mark.enums.QuestionModel;
+import com.qmth.teachcloud.mark.params.MarkArbitrateResult;
+import com.qmth.teachcloud.mark.params.MarkResult;
+import com.qmth.teachcloud.mark.params.MarkResultQuestion;
+import io.lettuce.core.GeoArgs.Sort;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * AI评卷相关 服务类
+ */
+public interface AiMarkService {
+
+    void releaseStudent(Long examId, String paperNumber, Long studentId, Long userId);
+
+    int applyCurrentCount(MarkQuestion markQuestion);
+
+    Set<Long> listCurrentStudent(Long examId, String paperNumber);
+
+    void releaseByMarkUserGroup(MarkUserQuestion markUserGroup);
+
+    int applyCurrentCount(MarkQuestion markQuestion, Long markUserGroupId);
+
+    void resetMarker(MarkUserQuestion markUserGroup);
+
+    void updateMarkedCount(Long examId, String paperNumber, Long questionId);
+
+    void updatePersonTask(Long examId, String paperNumber, Long questionId);
+
+    boolean rejectMarkTask(MarkProblemHistory markProblemHistory, MarkTask markTask, Long userId);
+
+    String getGroupKey(MarkQuestion markQuestion);
+
+    void updateQuality(MarkUserQuestion markUserGroup);
+
+    boolean needUpdateQuality(MarkUserQuestion marker, int expireMinutes);
+
+    void processArbitrate(MarkArbitrateResult markResult, Long userId);
+
+    void checkStudentSubjective(Long studentId, Long examId, String paperNumber, Integer version, boolean autoCalc);
+
+    void buildMarkTask(MarkPaper markPaper);
+
+    void deleteMarkTaskByStudent(MarkStudent student);
+
+    void updateGroupAllCount(Long examId, String paperNumber);
+
+    MarkSettingDto getSetting(SysUser user, Long examId, String paperNumber);
+
+    List<MarkStatusDto> getStatus(Long userId, Long examId, String paperNumber, QuestionModel questionModel, Long questionId);
+
+    Task getTask(Long userId, Long examId, String paperNumber, QuestionModel questionModel, Long questionId);
+
+    /**
+     * /** 释放某个大题的锁定任务
+     */
+//    void releaseByMarkGroup(MarkGroup markGroup);
+
+    void releaseByStudent(MarkStudent student);
+
+    /**
+     * 申请领取某个任务
+     */
+    boolean applyStudent(MarkStudent student, Long userId);
+
+    void submitHeaderTask(List<MarkResultQuestion> markResult, MarkStudent markUserGroup);
+
+    IPage<Task> getHistory(Long userId, int pageNumber, int pageSize, Sort sort, String order, Long examId, String paperNumber, String secretNumber, Double markerScore);
+
+    SubmitResult saveTask(Long examId, String paperNumber, Long userId, MarkResult markResult);
+
+    void clear(Long userId, Long examId, String paperNumber);
+
+    boolean applyTask(Long examId, String paperNumber, Long studentId, Long userId, Set<Long> questions, List<Long> taskIds);
+
+    boolean applyTask(Long examId, String paperNumber, Long studentId, Long userId, Set<Long> questions);
+
+    boolean hasApplied(MarkTask t, Long userId);
+
+    void deleteInitMarkData(Long examId, String paperNumber);
+
+    void calcObjectiveScore(MarkPaper markPaper);
+
+    void checkStudentSubjectiveScore(Long examId, String coursePaperId);
+
+    boolean rejectMarkTask(MarkTask markTask, Long userId, String reason);
+
+    void deleteMarkTask(MarkQuestion markQuestion, boolean b);
+
+    /**
+     * /** 释放整修科目的锁定任务
+     */
+    void releaseByMarkQuestion(MarkQuestion markQuestion);
+
+    void resetMarkedQuestionId(MarkUserQuestion markUserQuestion);
+
+    void aiMark(Long examId, String paperNumber, Long questionId);
+}

+ 1 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkTaskService.java

@@ -64,6 +64,7 @@ public interface MarkTaskService extends IService<MarkTask> {
     List<MarkTask> findUnMarked(Page<MarkTask> page, Long examId, String paperNumber, Long userId);
 
     List<MarkTask> findUnMarkedFilterClass(Page<MarkTask> page, Long examId, String paperNumber, Long userId, Long questionId, List<String> classNames);
+    List<MarkTask> findAiUnMarked(Page<MarkTask> page, Long examId, String paperNumber, Long questionId);
 
     int countByIdAndStatus(Long studentId, MarkTaskStatus status);
 

+ 1514 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/AiMarkServiceImpl.java

@@ -0,0 +1,1514 @@
+package com.qmth.teachcloud.mark.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qmth.boot.api.exception.ApiException;
+import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.entity.BasicCourse;
+import com.qmth.teachcloud.common.entity.MarkQuestion;
+import com.qmth.teachcloud.common.entity.SysUser;
+import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
+import com.qmth.teachcloud.common.enums.ScorePolicy;
+import com.qmth.teachcloud.common.enums.mark.*;
+import com.qmth.teachcloud.common.service.BasicCourseService;
+import com.qmth.teachcloud.common.service.BasicOperationLogService;
+import com.qmth.teachcloud.common.service.SysUserService;
+import com.qmth.teachcloud.common.util.ServletUtil;
+import com.qmth.teachcloud.mark.dto.mark.ScoreItem;
+import com.qmth.teachcloud.mark.dto.mark.manage.Task;
+import com.qmth.teachcloud.mark.dto.mark.mark.MarkSettingDto;
+import com.qmth.teachcloud.mark.dto.mark.mark.MarkStatusDto;
+import com.qmth.teachcloud.mark.dto.mark.mark.SubmitResult;
+import com.qmth.teachcloud.mark.entity.*;
+import com.qmth.teachcloud.mark.enums.ExamType;
+import com.qmth.teachcloud.mark.enums.LockType;
+import com.qmth.teachcloud.mark.enums.MarkTaskStatus;
+import com.qmth.teachcloud.mark.enums.QuestionModel;
+import com.qmth.teachcloud.mark.lock.LockService;
+import com.qmth.teachcloud.mark.params.MarkArbitrateResult;
+import com.qmth.teachcloud.mark.params.MarkResult;
+import com.qmth.teachcloud.mark.params.MarkResultQuestion;
+import com.qmth.teachcloud.mark.service.*;
+import com.qmth.teachcloud.mark.utils.BigDecimalUtils;
+import com.qmth.teachcloud.mark.utils.TaskLock;
+import com.qmth.teachcloud.mark.utils.TaskLockUtil;
+import io.lettuce.core.GeoArgs.Sort;
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Service
+public class AiMarkServiceImpl implements AiMarkService {
+
+    private final static Logger log = LoggerFactory.getLogger(AiMarkServiceImpl.class);
+
+    private Map<Long, Long> markerLastUpdateTime = new ConcurrentHashMap<>();
+
+    @Resource
+    private BasicCourseService basicCourseService;
+    @Resource
+    private MarkPaperService markPaperService;
+
+    @Resource
+    private MarkQuestionService markQuestionService;
+    @Resource
+    private MarkUserPaperService markUserPaperService;
+    @Resource
+    private MarkUserQuestionService markUserQuestionService;
+    @Resource
+    private MarkUserClassService markUserClassService;
+
+    @Resource
+    private MarkTaskService markTaskService;
+
+    @Resource
+    private MarkStudentService markStudentService;
+
+    @Resource
+    private MarkProblemHistoryService markProblemHistoryService;
+
+    @Resource
+    private MarkArbitrateHistoryService markArbitrateHistoryService;
+
+    @Resource
+    private MarkSubjectiveScoreService markSubjectiveScoreService;
+    @Resource
+    LockService lockService;
+
+    @Resource
+    ScanAnswerCardService scanAnswerCardService;
+
+    @Resource
+    TaskService taskService;
+
+    @Resource
+    private MarkHeaderHistoryService markHeaderHistoryService;
+    @Resource
+    private SysUserService sysUserService;
+    @Resource
+    private MarkRejectHistoryService markRejectHistoryService;
+    @Resource
+    private BasicOperationLogService basicOperationLogService;
+    @Resource
+    private MarkAiQuestionParamService markAiQuestionParamService;
+
+    /**
+     * 释放某个评卷员的考生
+     *
+     * @param exmId       考试ID
+     * @param paperNumber 试卷编号
+     * @param studentId   考生ID
+     * @param userId      用户ID
+     */
+    @Override
+    public void releaseStudent(Long exmId, String paperNumber, Long studentId, Long userId) {
+        if (studentId != null) {
+            TaskLock taskLock = TaskLockUtil.getFormalTask(getKey(exmId, paperNumber));
+            taskLock.remove(studentId, userId);
+            taskLock.refresh(userId);
+        }
+    }
+
+    /**
+     * 某个评卷分组已申请的评卷任务数量
+     *
+     * @return
+     */
+    @Override
+    public int applyCurrentCount(MarkQuestion markQuestion) {
+        TaskLock taskLock = getTaskLock(markQuestion);
+        int count = 0;
+        if (taskLock != null) {
+            count = taskLock.count(markQuestion.getId());
+        }
+        return count;
+    }
+
+    @Override
+    public Set<Long> listCurrentStudent(Long examId, String paperNumber) {
+        TaskLock taskLock = TaskLockUtil.getFormalTask(examId + "_" + paperNumber);
+        return taskLock.list().stream().map(m -> Long.valueOf(m.get("studentId").toString())).collect(Collectors.toSet());
+    }
+
+    private TaskLock getTaskLock(MarkQuestion markQuestion) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markQuestion.getExamId(),
+                markQuestion.getPaperNumber());
+        if (markPaper.getStatus() == MarkPaperStatus.FORMAL) {
+            return TaskLockUtil.getFormalTask(getKey(markQuestion.getExamId(), markQuestion.getPaperNumber()));
+        }
+        return null;
+    }
+
+    /**
+     * 释放某个评卷员的所有锁定任务
+     *
+     * @param markUserQuestion
+     */
+    @Override
+    public void releaseByMarkUserGroup(MarkUserQuestion markUserQuestion) {
+        TaskLock taskLock = TaskLockUtil.getFormalTask(getKey(markUserQuestion.getExamId(), markUserQuestion.getPaperNumber()));
+        taskLock.clear(markUserQuestion.getUserId());
+    }
+
+    @Override
+    public int applyCurrentCount(MarkQuestion markQuestion, Long userId) {
+        int count = 0;
+        TaskLock taskLock = getTaskLock(markQuestion);
+        if (taskLock != null) {
+            count = taskLock.count(markQuestion.getId(), userId);
+        }
+        return count;
+    }
+
+    @Override
+    public void resetMarker(MarkUserQuestion markUserQuestion) {
+        Long examId = markUserQuestion.getExamId();
+        String paperNumber = markUserQuestion.getPaperNumber();
+        Long questionId = markUserQuestion.getQuestionId();
+        Long userId = markUserQuestion.getUserId();
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        if (markPaper == null || markUserQuestionService.getById(markUserQuestion.getId()) == null) {
+            return;
+        }
+        if (markPaper.getStatus() == MarkPaperStatus.FORMAL) {
+            // 遍历相关评卷任务的模式
+            List<MarkTaskStatus> statusList = Arrays.asList(MarkTaskStatus.WAITING);
+            List<MarkTask> markTaskList = markTaskService.listByExamIdAndPaperNumberAndQuestionIdAndUserIdAndStatusNotIn(examId, paperNumber, questionId, userId, statusList);
+            for (MarkTask markTask : markTaskList) {
+                Long studentId = markTask.getStudentId();
+                if (MarkTaskStatus.ARBITRATED.equals(markTask.getStatus()) || MarkTaskStatus.WAIT_ARBITRATE.equals(markTask.getStatus())) {
+                    markTaskService.resetArbitrateStatusByStudentIdAndQuestionIdAndTaskNumber(studentId, questionId, markTask.getTaskNumber());
+                }
+                markTaskService.resetById(markTask.getId(), null, null, null, null, MarkTaskStatus.WAITING);
+                markSubjectiveScoreService.deleteByStudentIdAndQuestionId(studentId, questionId);
+                markProblemHistoryService.deleteByExamIdAndPaperNumberAndTaskId(examId, paperNumber, markTask.getId());
+                markRejectHistoryService.deleteByTaskId(markTask.getId());
+                markArbitrateHistoryService.deleteByExamIdAndPaperNumberAndStudentIdAndQuestionId(examId, paperNumber, studentId, questionId);
+                lockService.waitlock(LockType.STUDENT, markTask.getStudentId());
+                markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.UNMARK);
+                lockService.unlock(LockType.STUDENT, markTask.getStudentId());
+            }
+            markUserQuestionService.resetById(markUserQuestion.getId());
+
+        }
+        this.updateMarkedCount(examId, paperNumber, questionId);
+        releaseByMarkUserGroup(markUserQuestion);
+    }
+
+    @Override
+    public void updateMarkedCount(Long examId, String paperNumber, Long questionId) {
+        List<MarkTaskStatus> markTaskStatuses = Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED);
+        //查询已评和已仲裁的任务数
+        int count = markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndStatusIn(examId, paperNumber,
+                questionId, markTaskStatuses);
+        //更新当前小题的已评数量
+        markQuestionService.updateMarkedCount(questionId, count);
+    }
+
+    @Override
+    public void updatePersonTask(Long examId, String paperNumber, Long questionId) {
+        QueryWrapper<MarkTask> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkTask::getExamId, examId)
+                .eq(MarkTask::getPaperNumber, paperNumber)
+                .eq(MarkTask::getQuestionId, questionId)
+                .last(" limit 1");
+        MarkTask markTask = markTaskService.getOne(queryWrapper);
+
+        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkQuestion::getPersonTask, markTask != null)
+                .eq(MarkQuestion::getId, questionId);
+        markQuestionService.update(updateWrapper);
+    }
+
+    @Override
+    public boolean rejectMarkTask(MarkProblemHistory markProblemHistory, MarkTask markTask, Long userId) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markTask.getExamId(), markTask.getPaperNumber());
+        if (markPaper.getStatus() == MarkPaperStatus.FINISH) {
+            return false;
+        }
+        Long now = System.currentTimeMillis();
+        if (markTaskService.resetById(markTask.getId(), null, null, userId, now, MarkTaskStatus.WAITING)) {
+            resetStudentStatus(markTask.getStudentId());
+            markProblemHistoryService.resetByMarkProblemId(markProblemHistory.getId(), MarkProblemStatus.WAITING,
+                    userId, MarkProblemStatus.BACK, now);
+            updateMarkedCount(markTask.getExamId(), markTask.getPaperNumber(), markTask.getQuestionId());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private void resetStudentStatus(Long studentId) {
+        markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.UNMARK);
+    }
+
+
+    @Override
+    public String getGroupKey(MarkQuestion markQuestion) {
+        return getKey(markQuestion.getExamId(), markQuestion.getPaperNumber());
+    }
+
+    @Override
+    public void updateQuality(MarkUserQuestion markUserQuestion) {
+        List<MarkTask> list = markTaskService.listByExamIdAndPaperNumberAndQuestionIdAndUserIdAndClassName(
+                markUserQuestion.getExamId(), markUserQuestion.getPaperNumber(), markUserQuestion.getQuestionId(),
+                markUserQuestion.getUserId(), null);
+        int finishCount = 0;
+        int validCount = 0;
+        int headerFinishCount = 0;
+        double sumScore = 0;
+        double sumScore2 = 0;
+        double avgScore = 0;
+        double stdevScore = 0;
+        double sumSpent = 0;
+        double avgSpent = 0;
+        double maxScore = 0;
+        double minScore = 0;
+        for (MarkTask markTask : list) {
+            if (!markTask.getStatus().equals(MarkTaskStatus.PROBLEM)) {
+                finishCount++;
+            }
+            if (markTask.getStatus() == MarkTaskStatus.MARKED && markTask.getHeaderScore() == null) {
+                validCount++;
+            }
+            // 管理员打分数量(主观题检查,仲裁)
+            if ((markTask.getStatus() == MarkTaskStatus.MARKED || markTask.getStatus() == MarkTaskStatus.ARBITRATED) && markTask.getHeaderScore() != null) {
+                headerFinishCount++;
+            }
+            double score = markTask.getMarkerScore() != null ? markTask.getMarkerScore() : 0;
+            int spent = markTask.getMarkerSpent() != null ? markTask.getMarkerSpent() : 0;
+
+            sumScore += score;
+            sumScore2 += Math.pow(score, 2);
+            sumSpent += spent;
+            if (finishCount > 0) {
+                avgScore = sumScore / finishCount;
+                avgSpent = sumSpent / finishCount;
+                // 递归法计算标准差
+                stdevScore = Math.sqrt(sumScore2 / finishCount - Math.pow(sumScore / finishCount, 2));
+            }
+
+            // 最高分
+            if (score > maxScore) {
+                maxScore = score;
+            }
+            // 最低分
+            if (finishCount == 1) {
+                minScore = score;
+            } else if (score < minScore) {
+                minScore = score;
+            }
+        }
+        markUserQuestionService.updateQualityById(markUserQuestion.getId(), finishCount, headerFinishCount, validCount,
+                avgSpent / 1000, avgScore, stdevScore, maxScore, minScore);
+        markerLastUpdateTime.put(markUserQuestion.getId(), System.currentTimeMillis());
+    }
+
+    @Override
+    public boolean needUpdateQuality(MarkUserQuestion markUserQuestion, int expireMinutes) {
+        if (markUserQuestion == null) {
+            return false;
+        }
+        Long lastUpdateTime = markerLastUpdateTime.get(markUserQuestion.getId());
+        MarkTask markTask = markTaskService.getLastOneByUserIdAndStatus(markUserQuestion.getExamId(),
+                markUserQuestion.getPaperNumber(), markUserQuestion.getQuestionId(), markUserQuestion.getUserId(),
+                MarkTaskStatus.MARKED);
+        if (markTask != null && markTask.getMarkerTime() != null) {
+            long lastMarkTime = markTask.getMarkerTime();
+            if (lastUpdateTime != null && lastUpdateTime > lastMarkTime) {
+                return false;
+            } else {
+                return (System.currentTimeMillis() - lastMarkTime) < (expireMinutes * 60 * 1000);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 管理员/组长处理仲裁卷
+     *
+     * @param markResult
+     * @param userId
+     */
+    @Transactional
+    @Override
+    public void processArbitrate(MarkArbitrateResult markResult, Long userId) {
+        MarkArbitrateHistory markArbitrateHistory = markArbitrateHistoryService.getById(markResult.getArbitrateId());
+        markArbitrateHistory.setUpdateUserId(userId);
+        markArbitrateHistory.setTotalScore(markResult.getMarkerScore());
+//        markArbitrateHistory.setScoreList(markResult.isUnselective() ? null : markResult.getScoreList());
+        markArbitrateHistory.setStatus(MarkArbitrateStatus.MARKED);
+        markArbitrateHistory.setUpdateTime(System.currentTimeMillis());
+//        // 保存阅卷轨迹
+//        if (markResult.getTrackList() != null && !markResult.isUnselective()) {
+//            markHeaderTrackService.deleteByExamIdAndPaperNumberAndGroupNumberAndStudentId(
+//                    markArbitrateHistory.getExamId(), markArbitrateHistory.getPaperNumber(),
+//                    markArbitrateHistory.getGroupNumber(), markArbitrateHistory.getStudentId());
+//            List<MarkHeaderTrack> tracks = markResult.getTrackList(markArbitrateHistory);
+//            markHeaderTrackService.saveOrUpdateBatchByMultiId(tracks);
+//        }
+//        // 保存特殊标记
+//        if (markResult.getSpecialTagList() != null) {
+//            markHeaderTagService.deleteByStudentIdAndGroupNumber(markArbitrateHistory.getStudentId(),
+//                    markArbitrateHistory.getGroupNumber());
+//            markHeaderTagService.saveBatch(markResult.getHeaderTagList(markArbitrateHistory));
+//        }
+        markArbitrateHistoryService.saveOrUpdate(markArbitrateHistory);
+        markTaskService.updateHeaderResult(markArbitrateHistory.getExamId(), markArbitrateHistory.getPaperNumber(),
+                markArbitrateHistory.getQuestionId(), markArbitrateHistory.getStudentId(),
+                markArbitrateHistory.getUpdateUserId(), markResult.getMarkerScore(), markResult.getMarkerTrackList(), markResult.getMarkerTagList(), markArbitrateHistory.getUpdateTime(), MarkTaskStatus.ARBITRATED);
+        updateMarkedCount(markArbitrateHistory.getExamId(), markArbitrateHistory.getPaperNumber(),
+                markArbitrateHistory.getQuestionId());
+        MarkQuestion markQuestion = markQuestionService.getById(markArbitrateHistory.getQuestionId());
+        markQuestion.setMarkScore(markResult.getMarkerScore());
+        checkStudentQuestion(markArbitrateHistory.getStudentId(), markQuestion);
+        // 评卷质量重新统计
+        List<MarkUserQuestion> markUserGroups = markUserQuestionService.listByExamIdAndPaperNumberAndQuestionId(markArbitrateHistory.getExamId(), markArbitrateHistory.getPaperNumber(), markArbitrateHistory.getQuestionId());
+        markUserGroups.forEach(m -> this.updateQuality(m));
+    }
+
+    /**
+     * 考生分组判断是否评卷完成,以及后续的统一处理动作
+     *
+     * @param studentId
+     * @param markQuestion
+     */
+    private void checkStudentQuestion(Long studentId, MarkQuestion markQuestion) {
+        if (calculateQuestion(markQuestion, studentId)) {
+            //更新考生分组分数
+            updateStudentQuestionScore(studentId, markQuestion, markQuestion.getMarkScore());
+//            checkStudentSubjective(studentId, markQuestion.getExamId(), markQuestion.getPaperNumber(), version);
+        } else {
+            markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.UNMARK);
+        }
+    }
+
+    private void checkStudentQuestionForInspect(MarkStudent markStudent, MarkQuestion markQuestion) {
+        Long studentId = markStudent.getId();
+        if (calculateQuestion(markQuestion, studentId)) {
+            //更新考生分组分数
+            updateStudentQuestionScore(studentId, markQuestion, markQuestion.getMarkScore());
+            checkStudentSubjective(studentId, markQuestion.getExamId(), markQuestion.getPaperNumber(), markStudent.getVersion(), false);
+        } else {
+            markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.UNMARK);
+        }
+    }
+
+    @Override
+    @Transactional
+    public void checkStudentSubjective(Long studentId, Long examId, String paperNumber, Integer version, boolean autoCalc) {
+        try {
+            List<MarkQuestion> markQuestionList = markQuestionService.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
+            List<MarkSubjectiveScore> markSubjectiveScoreList = markSubjectiveScoreService.listByStudentIdAndExamIdAndPn(studentId, examId, paperNumber);
+            //校验阅卷题目和评分题目数量和题型是否一致
+            if (CollectionUtils.isNotEmpty(markQuestionList) && CollectionUtils.isNotEmpty(markSubjectiveScoreList)
+                    && this.megreQuestion(markQuestionList, markSubjectiveScoreList)) {
+                scoreCalculate(studentId, version, autoCalc);
+            }
+        } catch (Exception e) {
+            log.error(SystemConstant.LOG_ERROR, e);
+            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
+        }
+    }
+
+    /**
+     * 题型匹配
+     *
+     * @param markQuestionList
+     * @param markSubjectiveScoreList
+     * @return
+     */
+    protected boolean megreQuestion(List<MarkQuestion> markQuestionList, List<MarkSubjectiveScore> markSubjectiveScoreList) {
+        boolean calculate = true;
+        Map<String, MarkQuestion> markQuestionMap = markQuestionList.stream().collect(Collectors.toMap(k -> k.getMainNumber() + "-" + k.getSubNumber(), Function.identity(), (dto1, dto2) -> dto1));
+        Map<String, MarkSubjectiveScore> markSubjectiveScoreMap = markSubjectiveScoreList.stream().collect(Collectors.toMap(k -> k.getMainNumber() + "-" + k.getSubNumber(), Function.identity(), (dto1, dto2) -> dto1));
+        if (markQuestionMap.size() != markSubjectiveScoreMap.size()) {
+            calculate = false;
+        }
+        if (calculate) {
+            for (Map.Entry<String, MarkQuestion> entry : markQuestionMap.entrySet()) {
+                if (!markSubjectiveScoreMap.containsKey(entry.getKey())) {
+                    calculate = false;
+                    break;
+                }
+            }
+        }
+        return calculate;
+    }
+
+    @Override
+    public void buildMarkTask(MarkPaper markPaper) {
+        try {
+            lockService.watch(LockType.EXAM_SUBJECT, markPaper.getExamId(), markPaper.getPaperNumber());
+            log.info("start create mark_task for examId=" + markPaper.getExamId() + ", paperNumber="
+                    + markPaper.getPaperNumber());
+            // 清除缺考考生和违纪考生(违纪考生参与阅卷,update by 2024-10-17)
+            List<MarkStudent> markStudentList = markStudentService
+                    .listAbsentOrBreachMarkTaskStudent(markPaper.getExamId(), markPaper.getPaperNumber());
+            if (CollectionUtils.isNotEmpty(markStudentList)) {
+                for (MarkStudent student : markStudentList) {
+                    try {
+                        lockService.waitlock(LockType.STUDENT, student.getId());
+                        log.info("delete mark_task for studentId=" + student.getId());
+                        this.deleteMarkTaskByStudent(student);
+                    } catch (Exception e) {
+                        log.error("delete student mark_task error", e);
+                    } finally {
+                        lockService.unlock(LockType.STUDENT, student.getId());
+                    }
+                }
+            }
+            // 处理正常考生
+            List<MarkQuestion> markQuestionList = markQuestionService.listByExamIdAndPaperNumberAndObjective(markPaper.getExamId(), markPaper.getPaperNumber(), false);
+            for (MarkQuestion markQuestion : markQuestionList) {
+                if (!MarkPaperAiMark.NONE.equals(markQuestion.getAiMark()) && !markAiQuestionParamService.existMarkAiQuestionScoreOrLevel(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId())) {
+                    continue;
+                }
+                // 生成正评任务
+                buildFormalTask(markQuestion);
+                if (markQuestion.getDoubleRate() != null && markQuestion.getDoubleRate() > 0) {
+                    // 补双评任务
+                    fillFormalTask(markQuestion);
+                }
+            }
+        } finally {
+            lockService.unwatch(LockType.EXAM_SUBJECT, markPaper.getExamId(), markPaper.getPaperNumber());
+        }
+    }
+
+    private void buildFormalTask(MarkQuestion markQuestion) {
+        int pageSize = 100;
+        try {
+            lockService.watch(LockType.QUESTION, markQuestion.getId());
+            // 上锁后重复验证分组状态
+            MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markQuestion.getExamId(), markQuestion.getPaperNumber());
+            if (MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
+                return;
+            }
+            int count = 0;
+            List<MarkStudent> studentList = markStudentService.listUnMarkTaskStudent(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), pageSize);
+            while (CollectionUtils.isNotEmpty(studentList)) {
+                // 已生成的双评任务总数的第一组任务数
+                int doubleMarkTaskCount1 = markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndTaskNumber(
+                        markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), 1);
+                // 已生成的双评任务总数的第二组任务数
+                int doubleMarkTaskCount2 = markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndTaskNumber(
+                        markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), 2);
+                List<MarkTask> taskList = new ArrayList<>();
+                int doubleCount = 0;
+                int studentCount = studentList.size();
+                for (MarkStudent student : studentList) {
+                    MarkTask markTask = new MarkTask();
+                    markTask.setId(SystemConstant.getDbUuid());
+                    markTask.setExamId(student.getExamId());
+                    markTask.setCourseId(student.getCourseId());
+                    markTask.setPaperNumber(student.getPaperNumber());
+                    markTask.setQuestionId(markQuestion.getId());
+                    markTask.setMainNumber(markQuestion.getMainNumber());
+                    markTask.setSubNumber(markQuestion.getSubNumber());
+                    markTask.setStudentId(student.getId());
+                    markTask.setBasicStudentId(student.getBasicStudentId());
+                    markTask.setStudentCode(student.getStudentCode());
+                    markTask.setSecretNumber(student.getSecretNumber());
+                    markTask.setTaskNumber(1);
+                    markTask.setAiMarked(getAiMarked(markQuestion, markTask.getTaskNumber()));
+                    markTask.setStatus(MarkTaskStatus.WAITING);
+                    taskList.add(markTask);
+                    // 开启双评时需要判断是否生成第二份评卷任务
+                    if (markQuestion.getDoubleRate() != null && markQuestion.getDoubleRate() > 0) {
+                        boolean needDouble;
+                        if (markQuestion.getDoubleRate() == 100) {
+                            needDouble = true;
+                        } else {
+                            double libraryCount = taskList.size();
+                            int expectCount = (int) ((doubleMarkTaskCount1 + studentCount) * markQuestion.getDoubleRate()
+                                    / 100);
+                            // 随机数判断加入当前已经生成双评任务的比例加权
+                            // 实际双评任务数小于理论生成数 &&(剩余未生成双评的考生数量小于剩余应生成的数量)
+                            needDouble = (doubleMarkTaskCount2 + doubleCount) < expectCount
+                                    && ((studentCount - libraryCount + doubleCount) <= (expectCount
+                                    - doubleMarkTaskCount2 - doubleCount));
+                        }
+                        if (needDouble) {
+                            markTask = new MarkTask();
+                            markTask.setId(SystemConstant.getDbUuid());
+                            markTask.setExamId(student.getExamId());
+                            markTask.setCourseId(student.getCourseId());
+                            markTask.setPaperNumber(student.getPaperNumber());
+                            markTask.setQuestionId(markQuestion.getId());
+                            markTask.setMainNumber(markQuestion.getMainNumber());
+                            markTask.setSubNumber(markQuestion.getSubNumber());
+                            markTask.setStudentId(student.getId());
+                            markTask.setBasicStudentId(student.getBasicStudentId());
+                            markTask.setStudentCode(student.getStudentCode());
+                            markTask.setSecretNumber(student.getSecretNumber());
+                            markTask.setTaskNumber(2);
+                            markTask.setAiMarked(getAiMarked(markQuestion, markTask.getTaskNumber()));
+                            markTask.setStatus(MarkTaskStatus.WAITING);
+                            taskList.add(markTask);
+                            doubleCount++;
+                        }
+                    }
+                }
+                // 有新任务创建
+                if (CollectionUtils.isNotEmpty(taskList)) {
+                    count += taskList.size();
+                    // 任务乱序
+                    Collections.shuffle(taskList);
+                    // 批量保存
+                    markTaskService.saveBatch(taskList);
+                    // 正评状态才需要更新任务数量
+                    if (MarkPaperStatus.FORMAL.equals(markPaper.getStatus())) {
+                        this.updateMarkTaskCount(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+                        this.updateMarkedCount(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+                        this.updatePersonTask(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+                    }
+                }
+                studentList = markStudentService.listUnMarkTaskStudent(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), pageSize);
+            }
+            if (count != 0) {
+                log.info("finish create " + count + " markTask for examId=" + markQuestion.getExamId() + ", paperNumber="
+                        + markQuestion.getPaperNumber() + ", questionId=" + markQuestion.getId());
+            }
+        } catch (Exception e) {
+            log.error("build formal markTask error for examId=" + markQuestion.getExamId() + ", paperNumber="
+                    + markQuestion.getPaperNumber() + ", questionId=" + markQuestion.getId(), e);
+        } finally {
+            lockService.unwatch(LockType.QUESTION, markQuestion.getId());
+        }
+    }
+
+    private Boolean getAiMarked(MarkQuestion markQuestion, Integer taskNumber) {
+        if (MarkPaperAiMark.NONE.equals(markQuestion.getAiMark())) {
+            return false;
+        } else if (MarkPaperAiMark.AI_ONLY.equals(markQuestion.getAiMark()) || MarkPaperAiMark.MAN_MACHINE.equals(markQuestion.getAiMark())) {
+            return taskNumber == 1;
+        } else {
+            return false;
+        }
+    }
+
+    private void fillFormalTask(MarkQuestion markQuestion) {
+        int pageSize = 100;
+        try {
+            lockService.watch(LockType.QUESTION, markQuestion.getId());
+            // 上锁后重复验证分组状态
+            MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markQuestion.getExamId(), markQuestion.getPaperNumber());
+            if (MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
+                return;
+            }
+            int count = 0;
+            List<MarkStudent> studentList = markStudentService.listUnMarkDoubleTaskStudent(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), pageSize);
+            while (CollectionUtils.isNotEmpty(studentList)) {
+                // 已生成的双评任务总数的第一组任务数
+                int doubleMarkTaskCount1 = markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndTaskNumber(
+                        markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), 1);
+                // 已生成的双评任务总数的第二组任务数
+                int doubleMarkTaskCount2 = markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndTaskNumber(
+                        markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), 2);
+                List<MarkTask> taskList = new ArrayList<>();
+                int doubleCount = 0;
+                int studentCount = studentList.size();
+                for (MarkStudent student : studentList) {
+                    // 开启双评时需要判断是否生成第二份评卷任务
+                    if (markQuestion.getDoubleRate() != null && markQuestion.getDoubleRate() > 0) {
+                        boolean needDouble;
+                        if (markQuestion.getDoubleRate() == 100) {
+                            needDouble = true;
+                        } else {
+                            double libraryCount = taskList.size();
+                            int expectCount = (int) ((doubleMarkTaskCount1 + studentCount) * markQuestion.getDoubleRate()
+                                    / 100);
+                            // 随机数判断加入当前已经生成双评任务的比例加权
+                            // 实际双评任务数小于理论生成数 &&(剩余未生成双评的考生数量小于剩余应生成的数量)
+                            needDouble = (doubleMarkTaskCount2 + doubleCount) < expectCount
+                                    && ((studentCount - libraryCount + doubleCount) <= (expectCount
+                                    - doubleMarkTaskCount2 - doubleCount));
+                        }
+                        if (needDouble) {
+                            MarkTask markTask = new MarkTask();
+                            markTask.setId(SystemConstant.getDbUuid());
+                            markTask.setExamId(student.getExamId());
+                            markTask.setCourseId(student.getCourseId());
+                            markTask.setPaperNumber(student.getPaperNumber());
+                            markTask.setQuestionId(markQuestion.getId());
+                            markTask.setMainNumber(markQuestion.getMainNumber());
+                            markTask.setSubNumber(markQuestion.getSubNumber());
+                            markTask.setStudentId(student.getId());
+                            markTask.setBasicStudentId(student.getBasicStudentId());
+                            markTask.setStudentCode(student.getStudentCode());
+                            markTask.setSecretNumber(student.getSecretNumber());
+                            markTask.setTaskNumber(2);
+                            markTask.setAiMarked(getAiMarked(markQuestion, markTask.getTaskNumber()));
+                            markTask.setStatus(MarkTaskStatus.WAITING);
+                            taskList.add(markTask);
+                            doubleCount++;
+                        }
+                    }
+                }
+                // 有新任务创建
+                if (CollectionUtils.isNotEmpty(taskList)) {
+                    count += taskList.size();
+                    // 任务乱序
+                    Collections.shuffle(taskList);
+                    // 批量保存
+                    markTaskService.saveBatch(taskList);
+                    // 正评状态才需要更新任务数量
+                    if (MarkPaperStatus.FORMAL.equals(markPaper.getStatus())) {
+                        this.updateMarkTaskCount(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+                        this.updateMarkedCount(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+                        this.updatePersonTask(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+                    }
+                }
+                studentList = markStudentService.listUnMarkTaskStudent(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), pageSize);
+            }
+            if (count != 0) {
+                log.info("finish create " + count + " markTask for examId=" + markQuestion.getExamId() + ", paperNumber="
+                        + markQuestion.getPaperNumber() + ", questionId=" + markQuestion.getId());
+            }
+        } catch (Exception e) {
+            log.error("build formal markTask error for examId=" + markQuestion.getExamId() + ", paperNumber="
+                    + markQuestion.getPaperNumber() + ", questionId=" + markQuestion.getId(), e);
+        } finally {
+            lockService.unwatch(LockType.QUESTION, markQuestion.getId());
+        }
+    }
+
+    @Transactional
+    @Override
+    public void deleteMarkTaskByStudent(MarkStudent student) {
+        markStudentService.updateSubjectiveStatusAndScore(student.getId(), SubjectiveStatus.UNMARK);
+        // 正评相关数据
+        markArbitrateHistoryService.deleteByStudentId(student.getId());
+        markProblemHistoryService.deleteByStudentId(student.getId());
+        markTaskService.deleteByStudentId(student.getId());
+        // 主观状态与得分明细
+        markSubjectiveScoreService.deleteByStudentId(student.getId());
+        updateGroupAllCount(student.getExamId(), student.getPaperNumber());
+        // 复核记录
+        // inspectedService.clearByStudent(student.getId());
+        // 打回记录
+        // rejectHistoryDao.deleteByStudentId(student.getId());
+    }
+
+    @Transactional
+    @Override
+    public void updateGroupAllCount(Long examId, String paperNumber) {
+        List<MarkQuestion> markQuestionList = markQuestionService.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
+        for (MarkQuestion markQuestion : markQuestionList) {
+            this.updateMarkedCount(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+            this.updateMarkTaskCount(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+            this.updatePersonTask(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+        }
+    }
+
+    private void updateMarkTaskCount(Long examId, String paperNumber, Long questionId) {
+        int count = markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndUserId(examId, paperNumber, questionId,
+                null);
+        markQuestionService.updateTaskCount(questionId, count);
+    }
+
+    private boolean calculateQuestion(MarkQuestion markQuestion, Long studentId) {
+        double score = 0;
+        int count = 0;
+        // 未设置算分策略的情况下,默认取平均分
+        ScorePolicy policy = markQuestion.getScorePolicy() != null ? markQuestion.getScorePolicy() : ScorePolicy.AVG;
+        List<MarkTask> list = markTaskService.listByExamIdAndPaperNumberAndQuestionIdAndStudentId(markQuestion.getExamId(),
+                markQuestion.getPaperNumber(), markQuestion.getId(), studentId);
+        if (list.isEmpty()) {
+            return false;
+        }
+        for (MarkTask markTask : list) {
+            if (markTask.getStatus() != MarkTaskStatus.MARKED && markTask.getStatus() != MarkTaskStatus.ARBITRATED) {
+                // 有非完成状态的评卷任务,直接返回
+                return false;
+            }
+        }
+        for (MarkTask markTask : list) {
+            count++;
+            Double current = markTask.getHeaderScore() != null ? markTask.getHeaderScore() : markTask.getMarkerScore();
+            if (count == 1) {
+                // 首份评卷任务,直接取总分与明细
+                score = current;
+            } else {
+                switch (policy) {
+                    case AVG:
+                        // 直接累加
+                        score = BigDecimalUtils.add(score, current);
+                        break;
+                    case MAX:
+                        // 高分优先
+                        if (current > score) {
+                            score = current;
+                        }
+                        break;
+                    case MIN:
+                        // 低分优先
+                        if (current < score) {
+                            score = current;
+                        }
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+        // 取平均分策略下,累计分数需要重新计算一次
+        if (policy == ScorePolicy.AVG && count > 1) {
+            score = BigDecimalUtils.div(score, count);
+        }
+        markQuestion.setMarkScore(score);
+        return true;
+    }
+
+    private void updateStudentQuestionScore(Long studentId, MarkQuestion markQuestion, Double markScore) {
+        MarkSubjectiveScore ss = markSubjectiveScoreService.getByStudentIdAndQuestionId(studentId, markQuestion.getId());
+        if (ss == null) {
+            ss = new MarkSubjectiveScore();
+        }
+        ss.setStudentId(studentId);
+        ss.setExamId(markQuestion.getExamId());
+        ss.setPaperNumber(markQuestion.getPaperNumber());
+        ss.setQuestionId(markQuestion.getId());
+        ss.setMainNumber(markQuestion.getMainNumber());
+        ss.setSubNumber(markQuestion.getSubNumber());
+        ss.setScore(markScore);
+        ss.setMainScore(0.0);
+        markSubjectiveScoreService.saveOrUpdateByMultiId(ss);
+    }
+
+    private void scoreCalculate(Long studentId, Integer version, boolean autoCalc) {
+        List<ScoreItem> scoreList = new ArrayList<>();
+        Map<Integer, List<MarkSubjectiveScore>> mainScoreMap = new HashMap<>();
+        Map<Integer, Double> scoreMap = new HashMap<>();
+
+        // 循环所有主观得分明细
+        List<MarkSubjectiveScore> list = markSubjectiveScoreService.listByStudentId(studentId);
+        // list.sort(null);
+        for (MarkSubjectiveScore ss : list) {
+            List<MarkSubjectiveScore> mainScoreList = mainScoreMap.get(ss.getMainNumber());
+            if (mainScoreList == null) {
+                mainScoreList = new ArrayList<>();
+            }
+            mainScoreList.add(ss);
+            mainScoreMap.put(ss.getMainNumber(), mainScoreList);
+            scoreList.add(new ScoreItem(ss));
+        }
+        // 计算大题分
+        for (Integer mainNumber : mainScoreMap.keySet()) {
+            List<MarkSubjectiveScore> mainScoreList = mainScoreMap.get(mainNumber);
+            BigDecimal mainScore = BigDecimal.ZERO;
+            for (MarkSubjectiveScore subjectiveScore : mainScoreList) {
+                mainScore = mainScore.add(BigDecimal.valueOf(subjectiveScore.getScore()));
+            }
+            for (MarkSubjectiveScore subjectiveScore : mainScoreList) {
+                subjectiveScore.setMainScore(mainScore.doubleValue());
+                markSubjectiveScoreService.saveOrUpdateByMultiId(subjectiveScore);
+            }
+            scoreMap.put(mainNumber, mainScore.doubleValue());
+        }
+        // 计算选做题分数
+        BigDecimal totalScore = BigDecimal.ZERO;
+        // 计算非选做题总分
+        for (Integer mainNumber : scoreMap.keySet()) {
+            totalScore = totalScore.add(BigDecimal.valueOf(scoreMap.get(mainNumber)));
+        }
+        // 全部评完,更新考生主观题得分
+//        markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.MARKED, totalScore.doubleValue(),
+//                MarkStudent.buildScoreList(scoreList));
+        markStudentService.updateSubjectiveScoreByVersion(studentId, SubjectiveStatus.MARKED, totalScore.doubleValue(), MarkStudent.buildScoreList(scoreList), version, autoCalc);
+    }
+
+    @Override
+    public MarkSettingDto getSetting(SysUser user, Long examId, String paperNumber) {
+        MarkUserPaper markUserPaper = markUserPaperService.getByExamIdAndPaperNumberAndUserId(examId, paperNumber, user.getId());
+        user = sysUserService.getById(user.getId());
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+
+        MarkSettingDto dto = new MarkSettingDto();
+        dto.setExamType(ExamType.SCAN_IMAGE);//考试类型,默认SCAN_IMAGE
+        dto.setMode(markUserPaper != null && markUserPaper.getMode() != null ? markUserPaper.getMode()
+                : MarkMode.UNLIMITED.equals(markPaper.getMarkMode()) ? MarkMode.TRACK : markPaper.getMarkMode());//模式 TRACK/COMMON
+        dto.setQuestionModel(markUserPaper != null && markUserPaper.getQuestionModel() != null ? markUserPaper.getQuestionModel() : QuestionModel.MULTI);
+        dto.setForceMode(markPaper.getForceMode());//强制模式切换
+        dto.setSheetView(markPaper.getSheetView());//是否显示原图功能
+        dto.setSheetConfig(markPaper.getSheetConfig());//原图遮盖规则
+        dto.setEnableAllZore(false);//是否开启全零分(知学知考阅卷默认false)
+        dto.setFileServer(null);//图片服务地址
+        dto.setUserName(user.getRealName());//评卷员名称
+        dto.getSubject().setAnswerUrl(markQuestionService.previewAnswerFileByExamIdAndPaperNumber(examId, paperNumber));
+        dto.getSubject().setPaperUrl(markQuestionService.previewPaperFileByExamIdAndPaperNumber(examId, paperNumber));
+        BasicCourse basicCourse = basicCourseService.getById(markPaper.getCourseId());
+        dto.getSubject().setCode(markPaper.getPaperNumber());
+        dto.getSubject().setName(basicCourse != null ? basicCourse.getName() : null);
+        dto.setForceSpecialTag(false);//强制标记是否开启 必须要有标记(分数轨迹或特殊标记)(知学知考阅卷默认true)
+        dto.setUiSetting(user.getUiSetting());//新交互模式下,以下字段要重新定义或新增(初始值为空,按评卷员保存)
+        dto.setStatusValue(markPaper.getStatus());//只显示试评名称 FORMAL("正评"), FINISH("结束")
+        dto.setProblemTypes(MarkProblemType.listTypes());//问题卷类型
+//        dto.setTopCount(markUserGroup != null && markUserGroup.getTopCount() != null ? markUserGroup.getTopCount() : 0);
+        dto.setSplitConfig(new Double[0]);//使用裁切整图时的裁切配置
+        dto.setPrefetchCount(3);//预加载任务数量
+        dto.setStartTime(markPaper.getMarkStartTime());//评卷开始时间
+        dto.setEndTime(markPaper.getMarkEndTime());//评卷结束时间
+        dto.setSelective(false);//是否为选做题(默认false)
+        dto.setAutoScroll(markPaper.getAutoScroll());//是否自动跳转
+        dto.setEnableSplit(false);//是否裁切(默认false)
+        dto.setShowObjectScore(markPaper.getShowObjectScore());//是否显示客观分
+        return dto;
+    }
+
+    @Override
+    public List<MarkStatusDto> getStatus(Long userId, Long examId, String paperNumber, QuestionModel questionModel, Long questionId) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+
+        List<String> classNames = null;
+        //校验是否有分班阅
+        if (markPaper != null && markPaper.getClassMark() != null && markPaper.getClassMark().booleanValue()) {
+            List<MarkUserClass> markUserClassList = markUserClassService.listByExamIdAndPaperNumberAndUserId(examId, paperNumber, userId);
+            if (CollectionUtils.isNotEmpty(markUserClassList)) {
+                classNames = markUserClassList.stream().map(MarkUserClass::getClassName).collect(Collectors.toList());
+            }
+        }
+
+        List<MarkStatusDto> dtoList = new ArrayList<>();
+        List<MarkUserQuestion> markUserQuestionList = markUserQuestionService.listByExamIdAndPaperNumberAndUserIdAndEnableTure(examId, paperNumber, userId);
+        // 评卷方式
+        if (QuestionModel.SINGLE.equals(questionModel)) {
+            if (questionId != null) {
+                markUserQuestionList = markUserQuestionList.stream().filter(m -> m.getQuestionId().equals(questionId)).collect(Collectors.toList());
+            }
+            MarkUserPaper markUserPaper = markUserPaperService.getByExamIdAndPaperNumberAndUserId(examId, paperNumber, userId);
+            MarkStatusDto dto;
+            for (MarkUserQuestion question : markUserQuestionList) {
+                dto = new MarkStatusDto(markQuestionService.getById(question.getQuestionId()), markUserPaper.getMarkedQuestionId());
+                List<Long> questionIds = Arrays.asList(question.getQuestionId());
+                MarkStatusDto statusDto = getDto(dto, examId, paperNumber, userId, classNames, questionIds);
+                statusDto.setLeftCount(countLeftCountForSingle(examId, paperNumber, statusDto.getLeftCount(), question.getQuestionId(), userId, classNames));
+                dtoList.add(statusDto);
+            }
+        } else if (QuestionModel.MULTI.equals(questionModel)) {
+            MarkStatusDto dto = new MarkStatusDto();
+            List<Long> questionIds = markUserQuestionList.stream().filter(m -> m.getUserId().equals(userId)).map(MarkUserQuestion::getQuestionId).collect(Collectors.toList());
+            dtoList.add(getDto(dto, examId, paperNumber, userId, classNames, questionIds));
+        } else {
+            throw ExceptionResultEnum.ERROR.exception("参数有误");
+        }
+        dtoList.sort(Comparator.comparing(MarkStatusDto::getMainNumber).thenComparing(MarkStatusDto::getSubNumber));
+        return dtoList;
+    }
+
+    private int countLeftCountForSingle(Long examId, String paperNumber, int leftCount, Long questionId, Long userId, List<String> classNames) {
+        MarkQuestion markQuestion = markQuestionService.getById(questionId);
+        Set<Long> currentStudent = this.listCurrentStudent(examId, paperNumber);
+
+        int userCurrentCount = this.applyCurrentCount(markQuestion, userId);
+        if (CollectionUtils.isEmpty(classNames)) {
+            return leftCount - currentStudent.size() + userCurrentCount;
+        } else {
+//            List<MarkUserClass> markUserClassList = markUserClassService.listByExamIdAndPaperNumber(markQuestion.getExamId(), markQuestion.getPaperNumber());
+//            Set<MarkUserClass> markUserClasses = markUserClassList.stream().filter(m -> classNames.contains(m.getClassName()) && !m.getUserId().equals(userId)).collect(Collectors.toSet());
+//            int count = 0;
+//            for (MarkUserClass markUserClass : markUserClasses) {
+//                count += this.applyCurrentCount(markQuestion, markUserClass.getUserId());
+//            }
+//            return leftCount - count;
+            Set<Long> studentIds = markStudentService.listStudentIds(examId, paperNumber, classNames);
+            Collection<Long> collection = CollectionUtils.intersection(currentStudent, studentIds);
+            return leftCount - collection.size() + userCurrentCount;
+        }
+    }
+
+    private MarkStatusDto getDto(MarkStatusDto dto, Long examId, String paperNumber, Long userId, List<String> classNames, List<Long> questionIds) {
+        //待仲裁卷数量
+        dto.setArbitrateCount(markArbitrateHistoryService.waitArbitrateCount(examId, paperNumber, null, classNames));
+        //总数量
+        dto.setTotalCount(markTaskService.countByExamIdAndPaperNumberAndUserIdAndAndClassNameAndQuestionIdIn(examId, paperNumber, null, classNames, questionIds));
+        // 未评
+        int unmarkCount = markTaskService.countByExamIdAndPaperNumberAndUserIdAndAndClassNameAndQuestionIdIn(examId, paperNumber, null, classNames, questionIds, MarkTaskStatus.WAITING, MarkTaskStatus.WAIT_ARBITRATE, MarkTaskStatus.REJECTED);
+        dto.setLeftCount(unmarkCount);
+        //总评卷数量(考生数)
+        dto.setMarkedCount(dto.getTotalCount() == 0 ? 0 : dto.getTotalCount() - unmarkCount);
+        //个人评卷数量
+        dto.setPersonCount(markTaskService.countByExamIdAndPaperNumberAndUserIdAndAndClassNameAndQuestionIdIn(examId, paperNumber, userId, classNames, questionIds, MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED,
+                MarkTaskStatus.WAIT_ARBITRATE, MarkTaskStatus.PROBLEM));
+        //问题卷数量
+        dto.setProblemCount(markProblemHistoryService.countByExamIdAndPaperNumberAndStatusAndClassNameIn(examId, paperNumber, MarkProblemStatus.WAITING, classNames));
+        return dto;
+    }
+
+    @Override
+    public void clear(Long userId, Long examId, String paperNumber) {
+        TaskLock taskLock = TaskLockUtil.getFormalTask(getKey(examId, paperNumber));
+        taskLock.clear(userId);
+    }
+
+    @Override
+    public void releaseByStudent(MarkStudent student) {
+        TaskLock taskLock = TaskLockUtil.getInspectedStudentTask(getKey(student.getExamId(), student.getPaperNumber()));
+        taskLock.remove(student.getId());
+    }
+
+    @Override
+    public boolean applyStudent(MarkStudent student, Long userId) {
+        TaskLock taskLock = TaskLockUtil.getInspectedStudentTask(getKey(student.getExamId(), student.getPaperNumber()));
+        boolean lock = taskLock.add(student.getId(), userId, new HashSet<>());
+        // 上锁失败直接返回
+        if (!lock) {
+            return false;
+        }
+        // 重复校验任务状态
+        if (student.getSubjectiveStatus().equals(SubjectiveStatus.MARKED)) {
+            return true;
+        } else {
+            taskLock.remove(student.getId(), userId);
+            return false;
+        }
+    }
+
+    @Override
+    public void submitHeaderTask(List<MarkResultQuestion> resultQuestions, MarkStudent markStudent) {
+        Long userId = ServletUtil.getRequestUserId();
+        for (MarkResultQuestion result : resultQuestions) {
+            try {
+                lockService.watch(LockType.QUESTION, result.getQuestionId());
+                Long currentTime = System.currentTimeMillis();
+                updateMarkSubjectScore(markStudent, result, userId);
+                markTaskService.updateHeaderResult(markStudent.getExamId(), markStudent.getPaperNumber(),
+                        result.getQuestionId(), markStudent.getId(), userId, result.getMarkerScore(), result.getMarkerTrackList(), result.getMarkerTagList(), currentTime, MarkTaskStatus.MARKED);
+                updateMarkedCount(markStudent.getExamId(), markStudent.getPaperNumber(), result.getQuestionId());
+//                resetStudentStatus(markStudent.getId());
+                markStudentService.updateCheckInfo(markStudent.getId(), userId);
+                MarkQuestion markQuestion = markQuestionService.getById(result.getQuestionId());
+                markQuestion.setMarkScore(result.getMarkerScore());
+                checkStudentQuestionForInspect(markStudent, markQuestion);
+            } catch (ApiException e) {
+                throw ExceptionResultEnum.ERROR.exception(e.getMessage());
+            } finally {
+                lockService.unwatch(LockType.QUESTION, result.getQuestionId());
+            }
+        }
+    }
+
+    private void updateMarkSubjectScore(MarkStudent markStudent, MarkResultQuestion resultQuestion, Long userId) {
+        // 记录修改日志(按小题)
+//        List<BasicOperationLog> basicOperationLogs = new ArrayList<>();
+        MarkSubjectiveScore markSubjectiveScore = markSubjectiveScoreService.getByStudentIdAndQuestionId(markStudent.getId(), resultQuestion.getQuestionId());
+        double score = resultQuestion.getMarkerScore();
+        // 分数有变动,记录日志
+        if (markSubjectiveScore.getScore() != score) {
+//            MarkStudentVo markStudentVo = markStudentService.getDetailById(markStudent.getId());
+//            String detail = String.format("%s(%s)课程%s试卷编号下,将%s(%s)的%s题从%s分修改为%s分", markStudentVo.getCourseName(), markStudentVo.getCourseCode(), markStudentVo.getPaperNumber(), markStudentVo.getStudentName(), markStudentVo.getStudentCode(), markSubjectiveScore.getMainNumber() + "-" + markSubjectiveScore.getSubNumber(), markSubjectiveScore.getScore(), score);
+//            basicOperationLogs.add(new BasicOperationLog(Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString()), ServletUtil.getCurrentPrivilegeId(), OperationTypeEnum.SUBJECTIVE, OperationTypeEnum.SUBJECTIVE.getName(), ServletUtil.getRequest().getServletPath(), detail, String.valueOf(groupResult.getScore()), "成功", ServletUtil.getRequestUserId()));
+            MarkHeaderHistory headerHistory = new MarkHeaderHistory();
+            headerHistory.setId(SystemConstant.getDbUuid());
+            headerHistory.setExamId(markStudent.getExamId());
+            headerHistory.setPaperNumber(markStudent.getPaperNumber());
+            headerHistory.setStudentId(markStudent.getId());
+            headerHistory.setQuestionId(markSubjectiveScore.getQuestionId());
+            headerHistory.setMainNumber(markSubjectiveScore.getMainNumber());
+            headerHistory.setSubNumber(markSubjectiveScore.getSubNumber());
+            headerHistory.setUserId(userId);
+            headerHistory.setScore(score);
+            headerHistory.setTrackList(JSON.toJSONString(resultQuestion.getMarkerTrackList()));
+            headerHistory.setOriginalScore(markSubjectiveScore.getScore());
+//            headerHistory.setOriginalTrackList();
+            headerHistory.setCreateTime(System.currentTimeMillis());
+            markHeaderHistoryService.save(headerHistory);
+        }
+//        markSubjectiveScore.setScore(score);
+//        markSubjectiveScoreService.saveOrUpdateByMultiId(markSubjectiveScore);
+
+        // 记录日志
+//        if (CollectionUtils.isNotEmpty(basicOperationLogs)) {
+//            basicOperationLogService.saveBatch(basicOperationLogs);
+//        }
+    }
+
+    private String getKey(Long examId, String paperNumber) {
+        return examId + "_" + paperNumber;
+    }
+
+    @Override
+    public IPage<Task> getHistory(Long userId, int pageNumber, int pageSize, Sort sort, String order, Long examId,
+                                  String paperNumber, String secretNumber, Double markerScore) {
+        if (!"marker_time".equals(order) && !"marker_score".equals(order)) {
+            order = "marker_time";
+        }
+        Page<Long> page = new Page<>(pageNumber, pageSize);
+        OrderItem orderItem = new OrderItem(order, sort.equals(Sort.asc));
+        page.addOrder(orderItem);
+        IPage<MarkTask> list = markTaskService.listPageHistory(page, userId, examId, paperNumber, secretNumber, markerScore, MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED, MarkTaskStatus.WAIT_ARBITRATE, MarkTaskStatus.PROBLEM);
+        List<Task> recordsDtos = new ArrayList<>();
+        List<MarkTaskStatus> markTaskStatuses = Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED, MarkTaskStatus.WAIT_ARBITRATE, MarkTaskStatus.PROBLEM);
+        for (MarkTask markTask : list.getRecords()) {
+            List<MarkTask> markTaskList = markTaskService.listByStudentIdAndMarkerId(markTask.getStudentId(), userId, markTaskStatuses);
+            Task dto = taskService.build(userId, markTaskList);
+            dto.setMarkerTime(markTask.getMarkerTime());
+            recordsDtos.add(dto);
+        }
+        IPage<Task> result = new Page<>();
+        result.setCurrent(list.getCurrent());
+        result.setPages(list.getPages());
+        result.setRecords(recordsDtos);
+        result.setSize(list.getSize());
+        result.setTotal(list.getTotal());
+        return result;
+    }
+
+    @Override
+    public Task getTask(Long userId, Long examId, String paperNumber, QuestionModel questionModel, Long questionId) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        if (markPaper == null) {
+            throw ExceptionResultEnum.ERROR.exception("评卷试卷不存在");
+        }
+        int count = markUserQuestionService.countByExamIdAndPaperNumberAndAndUserId(examId, paperNumber, userId);
+        if (count == 0) {
+            throw ExceptionResultEnum.ERROR.exception("评卷任务被重置,请点击右上角返回按钮,重新在评卷入口菜单点击评卷");
+        }
+        List<MarkUserQuestion> markUserQuestions = markUserQuestionService.listByExamIdAndPaperNumberAndUserIdAndEnableTure(examId, paperNumber, userId);
+        if (markUserQuestions.isEmpty()) {
+            throw ExceptionResultEnum.ERROR.exception("评卷员未设置评卷题目");
+        }
+        List<String> classNames = null;
+        //校验是否有分班阅
+        if (markPaper != null && markPaper.getClassMark() != null && markPaper.getClassMark().booleanValue()) {
+            List<MarkUserClass> markUserClassList = markUserClassService.listByExamIdAndPaperNumberAndUserId(examId, paperNumber, userId);
+            if (CollectionUtils.isNotEmpty(markUserClassList)) {
+                classNames = markUserClassList.stream().map(MarkUserClass::getClassName).collect(Collectors.toList());
+            }
+        }
+
+        Task task = null;
+        List<Long> studentIds;
+        int pageNumber = 1;
+        while (task == null) {
+            if (questionModel.equals(QuestionModel.SINGLE)) {
+                Set<Long> questions = new HashSet<>(Arrays.asList(questionId));
+                List<MarkTask> list = markTaskService.findUnMarkedFilterClass(new Page<>(pageNumber, 20), examId, paperNumber, userId, questionId, classNames);
+                if (list.isEmpty()) {
+                    break;
+                }
+                for (MarkTask t : list) {
+                    if (this.applyTask(examId, paperNumber, t.getStudentId(), userId, questions, Arrays.asList(t.getId()))) {
+                        task = taskService.build(userId, Arrays.asList(t));
+                        break;
+                    }
+                }
+            } else if (questionModel.equals(QuestionModel.MULTI)) {
+                Set<Long> questions = markUserQuestions.stream().map(MarkUserQuestion::getQuestionId).collect(Collectors.toSet());
+                studentIds = markStudentService.findUnMarked(new Page<>(pageNumber, 20), examId, paperNumber, questions, classNames);
+                if (studentIds.isEmpty()) {
+                    break;
+                }
+                for (Long studentId : studentIds) {
+                    if (this.applyTask(examId, paperNumber, studentId, userId, questions)) {
+                        List<MarkTask> markTaskList = markTaskService.listByStudentId(studentId);
+                        if (markTaskList.isEmpty()) {
+                            releaseStudent(examId, paperNumber, studentId, userId);
+                            continue;
+                        }
+                        Map<Long, List<MarkTask>> map = markTaskList.stream().collect(Collectors.groupingBy(MarkTask::getQuestionId));
+                        List<MarkTask> markTasks = new ArrayList<>();
+                        for (MarkUserQuestion markUserQuestion : markUserQuestions) {
+                            List<MarkTask> markTasks1 = map.get(markUserQuestion.getQuestionId());
+                            if (markTasks1.size() == 1) {
+                                markTasks.add(markTasks1.get(0));
+                            } else {
+                                MarkTask markTask = markTasks1.stream().filter(m -> userId.equals(m.getUserId())).findFirst().orElse(null);
+                                if (markTask == null) {
+                                    markTask = markTasks1.stream().filter(m -> m.getUserId() == null).findFirst().orElse(null);
+                                    if (markTask == null) {
+                                        markTask = markTasks1.get(0);
+                                    }
+                                }
+                                markTasks.add(markTask);
+                            }
+                        }
+                        // 所有题目都已评,跳过
+                        if (markTasks.stream().filter(m -> m.getUserId() == null).count() == 0) {
+                            releaseStudent(examId, paperNumber, studentId, userId);
+                            continue;
+                        }
+                        markTasks.sort(Comparator.comparing(MarkTask::getMainNumber).thenComparing(MarkTask::getSubNumber));
+                        task = taskService.build(userId, markTasks);
+                        break;
+                    }
+                }
+            } else {
+                break;
+            }
+
+            if (task == null) {
+                pageNumber++;
+            }
+        }
+        return task;
+    }
+
+    @Override
+    public boolean applyTask(Long examId, String paperNumber, Long studentId, Long userId, Set<Long> questions, List<Long> taskIds) {
+        // 查询待领取任务时,已经做了多评同一studentId互斥处理
+        TaskLock taskLock = TaskLockUtil.getFormalTask(getKey(examId, paperNumber));
+        boolean lock = taskLock.add(studentId, userId, questions);
+        // 上锁失败直接返回
+        if (!lock) {
+            return false;
+        }
+        // 检验是否有待评任务
+        if (markTaskService.countByStatusAndIdIn(taskIds, MarkTaskStatus.WAITING, MarkTaskStatus.REJECTED) > 0) {
+            return true;
+        } else {
+            taskLock.remove(studentId, userId);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean applyTask(Long examId, String paperNumber, Long studentId, Long userId, Set<Long> questions) {
+        // 查询待领取任务时,已经做了多评同一studentId互斥处理
+        TaskLock taskLock = TaskLockUtil.getFormalTask(getKey(examId, paperNumber));
+        boolean lock = taskLock.add(studentId, userId, questions);
+        // 上锁失败直接返回
+        if (!lock) {
+            return false;
+        }
+//        taskLock.remove(studentId, userId);
+        return true;
+    }
+
+    @Override
+    public boolean hasApplied(MarkTask t, Long userId) {
+        TaskLock taskLock = TaskLockUtil.getFormalTask(getKey(t.getExamId(), t.getPaperNumber()));
+        return taskLock.exist(t.getStudentId(), userId);
+    }
+
+    @Override
+    public void deleteInitMarkData(Long examId, String paperNumber) {
+        markPaperService.deleteByExamIdAndPaperNumber(examId, paperNumber);
+        markStudentService.deleteByExamIdAndPaperNumber(examId, paperNumber);
+        scanAnswerCardService.deleteByExamIdAndPaperNumber(examId, paperNumber);
+        markQuestionService.deleteByExamIdAndPaperNumber(examId, paperNumber);
+    }
+
+    @Override
+    public void calcObjectiveScore(MarkPaper markPaper) {
+        int pageNumber = 1;
+        int pageSize = 1000;
+        IPage<MarkStudent> iPage = markStudentService.pageByExamAndPaperNumber(markPaper.getExamId(),
+                markPaper.getPaperNumber(), pageNumber, pageSize);
+        while (CollectionUtils.isNotEmpty(iPage.getRecords())) {
+            for (MarkStudent student : iPage.getRecords()) {
+                calculate(student);
+            }
+            pageNumber++;
+            iPage = markStudentService.pageByExamAndPaperNumber(markPaper.getExamId(), markPaper.getPaperNumber(),
+                    pageNumber, pageSize);
+        }
+    }
+
+    private void calculate(MarkStudent student) {
+        // 未上传、缺考的考生不统分
+        if (!student.getUpload() || student.getAbsent() || student.getOmrAbsent() || (student.getManualAbsent() != null && student.getManualAbsent())) {
+            return;
+        }
+        try {
+            // 更新客观题得分
+            markStudentService.calculateObjectiveScore(student);
+            // 增加主观题总分统计
+            // markService.scoreCalculate(student,
+            // findMarkGroup(student.getSubjectCode()));
+        } catch (Exception e) {
+            log.error("calculate error for studentId=" + student.getId(), e);
+        }
+    }
+
+    @Transactional
+    @Override
+    public SubmitResult saveTask(Long examId, String paperNumber, Long userId, MarkResult result) {
+        for (MarkResultQuestion question : result.getQuestionList()) {
+            MarkUserQuestion markUserQuestion = markUserQuestionService.getByExamIdAndPaperNumberAndQuestionIdAndUserId(examId, paperNumber, question.getQuestionId(), userId);
+            if (markUserQuestion == null) {
+                throw ExceptionResultEnum.ERROR.exception("评卷员未绑定题目" + question.getMainNumber() + "-" + question.getSubNumber());
+            }
+        }
+
+        SubmitResult sr;
+        try {
+            lockService.watch(LockType.EXAM_SUBJECT, examId, paperNumber);
+            lockService.watch(LockType.MARK_USER_QUESTION, userId);
+            lockService.waitlock(LockType.STUDENT, result.getStudentId());
+            sr = submitResult(examId, paperNumber, userId, result);
+            if (sr.isSuccess()) {
+                releaseStudent(examId, paperNumber, sr.getStudentId(), userId);
+            }
+        } catch (Exception e) {
+            log.error("save task error", e);
+            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
+        } finally {
+            lockService.unlock(LockType.STUDENT, result.getStudentId());
+            lockService.unwatch(LockType.MARK_USER_QUESTION, userId);
+            lockService.unwatch(LockType.EXAM_SUBJECT, examId, paperNumber);
+        }
+
+        if (sr == null || !sr.isSuccess()) {
+            throw ExceptionResultEnum.ERROR.exception("评卷任务提交失败,请刷新页面");
+        }
+        return sr;
+    }
+
+    private SubmitResult submitResult(Long examId, String paperNumber, Long userId, MarkResult result) {
+        int spentAvg = result.getSpent() / result.getQuestionList().size();
+        int count = 0;
+        for (MarkResultQuestion markResultQuestion : result.getQuestionList()) {
+            try {
+                lockService.watch(LockType.QUESTION, markResultQuestion.getQuestionId());
+                markResultQuestion.setSpent(spentAvg);
+                MarkUserQuestion markUserQuestion = markUserQuestionService.getByExamIdAndPaperNumberAndQuestionIdAndUserId(examId, paperNumber, markResultQuestion.getQuestionId(), userId);
+                MarkQuestion markQuestion = markQuestionService.getById(markResultQuestion.getQuestionId());
+                MarkTask task = markTaskService.getById(markResultQuestion.getTaskId());
+                if (task != null && task.getExamId().equals(markUserQuestion.getExamId())
+                        && task.getPaperNumber().equals(markUserQuestion.getPaperNumber())
+                        && task.getQuestionId().equals(markUserQuestion.getQuestionId())) {
+                    // 问题卷
+                    Long now = System.currentTimeMillis();
+                    if (markResultQuestion.isProblem()) {
+                        // 状态更新
+                        if (markTaskService.updateProblemResult(task.getId(), userId, now, markResultQuestion.getSpent())) {
+                            saveProblemHistory(markResultQuestion, task, userId);
+                            updateMarkedCount(markUserQuestion.getExamId(), markUserQuestion.getPaperNumber(), markUserQuestion.getQuestionId());
+                            // 未评完
+                            resetStudentStatus(task.getStudentId());
+                            count++;
+                        }
+                    } else if (MarkTaskStatus.ARBITRATED.equals(task.getStatus()) || MarkTaskStatus.WAIT_ARBITRATE.equals(task.getStatus())) {
+                        // 待仲裁、已仲裁,直接跳过
+                        count++;
+                    } else if (markResultQuestion.getMarkerScore() <= markQuestion.getTotalScore()) {//阅卷分是否小于等于该组总分
+                        if (submitTask(task, userId, markQuestion, markResultQuestion)) {
+                            updateMarkedCount(markUserQuestion.getExamId(), markUserQuestion.getPaperNumber(), markUserQuestion.getQuestionId());
+                            count++;
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                throw ExceptionResultEnum.ERROR.exception(e.getMessage());
+            } finally {
+                lockService.unwatch(LockType.QUESTION, markResultQuestion.getQuestionId());
+            }
+        }
+        if (CollectionUtils.size(result.getQuestionList()) == count) {
+            // 更新当前正在阅卷的questionId
+            markUserPaperService.update(examId, paperNumber, userId, null, null, result.getMarkedQuestionId());
+            return SubmitResult.success(result.getStudentId());
+        } else {
+            return SubmitResult.faile();
+        }
+    }
+
+    private void saveProblemHistory(MarkResultQuestion result, MarkTask task, Long userId) {
+        MarkProblemHistory history = markProblemHistoryService.findByTaskIdAndStatus(task.getId(),
+                MarkProblemStatus.WAITING);
+        if (history == null) {
+            history = new MarkProblemHistory();
+            history.setId(SystemConstant.getDbUuid());
+        }
+        history.setExamId(task.getExamId());
+        history.setSecretNumber(task.getSecretNumber());
+        history.setStudentCode(task.getStudentCode());
+        history.setStudentId(task.getStudentId());
+        history.setTaskId(task.getId());
+        history.setPaperNumber(task.getPaperNumber());
+        history.setQuestionId(task.getQuestionId());
+        history.setType(result.getProblemType());
+        if (MarkProblemType.OTHER.equals(result.getProblemType())) {
+            history.setRemark(result.getProblemRemark());
+        }
+        history.setCreateTime(System.currentTimeMillis());
+        history.setStatus(MarkProblemStatus.WAITING);
+        history.setUserId(userId);
+        markProblemHistoryService.saveOrUpdate(history);
+    }
+
+    private boolean submitTask(MarkTask task, Long userId, MarkQuestion markQuestion, MarkResultQuestion result) {
+        // 非本人领取的待评任务
+        if ((task.getStatus() == MarkTaskStatus.WAITING || task.getStatus() == MarkTaskStatus.REJECTED)
+                && !hasApplied(task, userId)) {
+            return false;
+        }
+        // 非本人的回评任务
+        if ((task.getStatus() == MarkTaskStatus.MARKED) && !task.getUserId().equals(userId)) {
+            return false;
+        }
+        // 是否多评情况下已处理过该考生评卷任务
+        if (markTaskService.countByStudentIdAndMarkerIdAndIdNotEqual(task.getStudentId(), task.getExamId(),
+                task.getPaperNumber(), task.getQuestionId(), userId, task.getId()) > 0) {
+            return false;
+        }
+        // 尝试提交评卷结果
+        Long now = System.currentTimeMillis();
+        //更新阅卷任务状态为已阅卷[阅卷分数,给分明细,阅卷时间,评卷时长]
+        if (!markTaskService.updateMarkerResult(task.getId(), MarkTaskStatus.MARKED, userId,
+                result, now, null, null, null, MarkTaskStatus.WAITING,
+                MarkTaskStatus.MARKED, MarkTaskStatus.REJECTED)) {
+            // 条件不符更新失败,直接返回
+            return false;
+        }
+        // 判断多评模式下是否需要仲裁
+        MarkArbitrateHistory history = null;
+        if (markQuestion.getArbitrateThreshold() != null && markQuestion.getArbitrateThreshold() > 0) {
+            // 多评模式
+            List<MarkTask> list = markTaskService.findByStudentIdAndQuestionIdAndStatus(task.getStudentId(), task.getQuestionId(), MarkTaskStatus.MARKED);
+            for (MarkTask other : list) {
+                // 本评卷任务或组长已打分,则跳过该任务
+                if (other.getId().equals(task.getId()) || other.getHeaderScore() != null) {
+                    continue;
+                }
+                // 分差超过阀值
+                if (Math.abs(other.getMarkerScore() - result.getMarkerScore()) > markQuestion.getArbitrateThreshold()) {
+                    history = buildArbitrateHistory(task, now);
+                    break;
+                }
+            }
+        }
+        if (history != null) {
+            // 保存仲裁记录
+            markArbitrateHistoryService.save(history);
+            // 触发仲裁后续状态更新
+            markTaskService.updateStatusByStudentIdAndQuestionId(task.getStudentId(), task.getQuestionId(), MarkTaskStatus.WAIT_ARBITRATE);
+            // 未评完
+            resetStudentStatus(task.getStudentId());
+        } else {
+            // 回评时,分数不一致,需要重新统分
+            if (task.getMarkerScore() != null && !task.getMarkerScore().equals(result.getMarkerScore())) {
+                resetStudentStatus(task.getStudentId());
+                markTaskService.resetHeaderByStudentIdAndQuestionId(task.getStudentId(), task.getQuestionId());
+                markHeaderHistoryService.deleteByStudentIdAndQuestionId(task.getStudentId(), task.getQuestionId());
+            }
+            // 判断当前分组是否已完成评卷
+            markQuestion.setMarkScore(result.getMarkerScore());
+            checkStudentQuestion(task.getStudentId(), markQuestion);
+        }
+        return true;
+    }
+
+    private MarkArbitrateHistory buildArbitrateHistory(MarkTask task, Long now) {
+        MarkArbitrateHistory history = new MarkArbitrateHistory();
+        history.setId(SystemConstant.getDbUuid());
+        history.setExamId(task.getExamId());
+        history.setPaperNumber(task.getPaperNumber());
+        history.setQuestionId(task.getQuestionId());
+        history.setStudentId(task.getStudentId());
+        history.setStudentCode(task.getStudentCode());
+        history.setSecretNumber(task.getSecretNumber());
+        history.setStatus(MarkArbitrateStatus.WAITING);
+        history.setCreateTime(now);
+        return history;
+    }
+
+    @Override
+    public void checkStudentSubjectiveScore(Long examId, String coursePaperId) {
+        List<MarkStudent> markStudentList = markStudentService.listByExamIdAndCoursePaperId(examId, coursePaperId);
+        for (MarkStudent markStudent : markStudentList) {
+            resetStudentStatus(markStudent.getId());
+        }
+    }
+
+    @Override
+    @Transactional
+    public boolean rejectMarkTask(MarkTask markTask, Long userId, String reason) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markTask.getExamId(), markTask.getPaperNumber());
+        if (MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
+            throw ExceptionResultEnum.MARK_PAPER_FINISH.exception();
+        }
+        MarkRejectHistory history = new MarkRejectHistory(markTask);
+        MarkStudent markStudent = markStudentService.getById(markTask.getStudentId());
+        history.setBasicStudentId(markStudent.getBasicStudentId());
+        history.setRejectUserId(userId);
+        history.setRejectReason(reason);
+        Long now = System.currentTimeMillis();
+        if (markTaskService.resetById(markTask.getId(), null, reason, userId, now, MarkTaskStatus.REJECTED)) {
+            markUserQuestionService.updateRejectCountByExamIdAndPaperNumberAndQuestionIdAndUserId(markTask.getExamId(), markTask.getPaperNumber(), markTask.getQuestionId(), markTask.getUserId());
+            markRejectHistoryService.save(history);
+//            markSubjectiveScoreService.updateRejected(markTask.getStudentId(), markTask.getQuestionId(), true);
+            markSubjectiveScoreService.deleteByStudentIdAndQuestionId(markTask.getStudentId(), markTask.getQuestionId());
+            resetStudentStatus(markTask.getStudentId());
+            markTaskService.resetHeaderByStudentIdAndQuestionId(markTask.getStudentId(), markTask.getQuestionId());
+            markHeaderHistoryService.deleteByStudentIdAndQuestionId(markTask.getStudentId(), markTask.getQuestionId());
+            updateMarkedCount(markTask.getExamId(), markTask.getPaperNumber(), markTask.getQuestionId());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void deleteMarkTask(MarkQuestion markQuestion, boolean b) {
+        // 正评相关数据
+        if (b) {
+            markArbitrateHistoryService.deleteByExamIdAndPaperNumberAndQuestionId(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+            markProblemHistoryService.deleteByExamIdAndPaperNumberAndQuestionId(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+            markTaskService.deleteByExamIdAndPaperNumberAndQuestionId(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+            markRejectHistoryService.deleteByExamIdAndPaperNumberAndQuestionId(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+        }
+        markSubjectiveScoreService.deleteByExamIdAndPaperNumberAndQuestionId(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+        // 释放本小题所有评卷员的任务
+        List<MarkUserQuestion> markUserQuestions = markUserQuestionService.listByExamIdAndPaperNumberAndQuestionId(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+        for (MarkUserQuestion markUserQuestion : markUserQuestions) {
+            releaseByMarkUserGroup(markUserQuestion);
+        }
+    }
+
+    @Override
+    public void releaseByMarkQuestion(MarkQuestion markQuestion) {
+        TaskLock taskLock = getTaskLock(markQuestion);
+        taskLock.clear();
+    }
+
+    @Override
+    public void resetMarkedQuestionId(MarkUserQuestion markUserQuestion) {
+        List<MarkQuestion> markQuestionList = markQuestionService.listByExamIdAndPaperNumberAndObjective(markUserQuestion.getExamId(), markUserQuestion.getPaperNumber(), false);
+        List<Long> list = markQuestionList.stream().map(MarkQuestion::getId).collect(Collectors.toList());
+        MarkUserPaper markUserPaper = markUserPaperService.getByExamIdAndPaperNumberAndUserId(markUserQuestion.getExamId(), markUserQuestion.getPaperNumber(), markUserQuestion.getUserId());
+        if (markUserPaper != null && QuestionModel.SINGLE.equals(markUserPaper.getQuestionModel()) && markUserPaper.getMarkedQuestionId() != null) {
+            int currentQuestionIdIndex = list.indexOf(markUserQuestion.getQuestionId());
+            int oldQuestionIdIndex = list.indexOf(markUserPaper.getMarkedQuestionId());
+            if (currentQuestionIdIndex < oldQuestionIdIndex) {
+                markUserPaper.setMarkedQuestionId(markUserQuestion.getQuestionId());
+                markUserPaperService.updateById(markUserPaper);
+            }
+        }
+    }
+
+    @Override
+    public void aiMark(Long examId, String paperNumber, Long questionId) {
+        List<MarkTask> markTasks = null;
+        int pageNumber = 1;
+        while (CollectionUtils.isNotEmpty(markTasks)) {
+            Set<Long> questions = new HashSet<>(Arrays.asList(questionId));
+            markTasks = markTaskService.findAiUnMarked(new Page<>(pageNumber, 20), examId, paperNumber, questionId);
+            if (markTasks.isEmpty()) {
+                break;
+            }
+            for (MarkTask t : markTasks) {
+                if (this.applyTask(examId, paperNumber, t.getStudentId(), null, questions, Arrays.asList(t.getId()))) {
+                    break;
+                }
+            }
+            pageNumber++;
+        }
+    }
+
+}

+ 5 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkTaskServiceImpl.java

@@ -262,6 +262,11 @@ public class MarkTaskServiceImpl extends ServiceImpl<MarkTaskMapper, MarkTask> i
         return this.baseMapper.findUnMarkedFilterClass(page, examId, paperNumber, userId, questionId, classNames);
     }
 
+    @Override
+    public List<MarkTask> findAiUnMarked(Page<MarkTask> page, Long examId, String paperNumber, Long questionId) {
+        return this.baseMapper.findAiUnMarked(page, examId, paperNumber, questionId);
+    }
+
     @Override
     public int countByIdAndStatus(Long studentId, MarkTaskStatus status) {
         QueryWrapper<MarkTask> queryWrapper = new QueryWrapper<>();

+ 14 - 0
teachcloud-mark/src/main/resources/mapper/MarkTaskMapper.xml

@@ -231,6 +231,20 @@
         </if>
         </where>
     </select>
+    <select id="findAiUnMarked" resultType="com.qmth.teachcloud.mark.entity.MarkTask">
+        SELECT
+        mt.*
+        FROM
+        mark_task mt
+        <where>
+            mt.exam_id = #{examId}
+            AND mt.paper_number = #{paperNumber}
+            AND mt.question_id = #{questionId}
+            AND mt.user_id is null
+            AND mt.ai_marked = true
+            AND mt.status='WAITING'
+        </where>
+    </select>
     <select id="listByExamIdAndPaperNumberAndQuestionIdAndUserIdAndClassName"
             resultType="com.qmth.teachcloud.mark.entity.MarkTask">
         SELECT