浏览代码

代码修复

wangliang 1 年之前
父节点
当前提交
be4d656467

+ 4 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/params/ExamCardParams.java

@@ -21,18 +21,22 @@ public class ExamCardParams extends ExamCard {
         this.examTaskId = examTaskId;
     }
 
+    @Override
     public String getContent() {
         return content;
     }
 
+    @Override
     public void setContent(String content) {
         this.content = content;
     }
 
+    @Override
     public String getHtmlContent() {
         return htmlContent;
     }
 
+    @Override
     public void setHtmlContent(String htmlContent) {
         this.htmlContent = htmlContent;
     }

+ 1 - 1
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/ExamTaskServiceImpl.java

@@ -2148,7 +2148,7 @@ public class ExamTaskServiceImpl extends ServiceImpl<ExamTaskMapper, ExamTask> i
                 examDetailCourseService.save(examDetailCourse);
             } else {
                 examDetail = examDetailService.getById(makeupExamTaskParam.getExamDetailId());
-                if (examDetail.getTotalSubjects() != makeupExamTaskParam.getTotalSubjects()) {
+                if (Objects.nonNull(examDetail.getTotalSubjects()) && Objects.nonNull(makeupExamTaskParam.getTotalSubjects()) && examDetail.getTotalSubjects().intValue() != makeupExamTaskParam.getTotalSubjects().intValue()) {
                     examDetail.setTotalSubjects(makeupExamTaskParam.getTotalSubjects());
                     examDetail.setStatus(ExamDetailStatusEnum.NEW);
                     examDetailService.updateById(examDetail);

+ 1 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/dto/mark/ScoreItem.java

@@ -83,6 +83,7 @@ public class ScoreItem {
         this.objective = objective;
     }
 
+    @Override
     public String toString() {
         return objective ? answer + SPLIT + getScoreString() : getScoreString();
     }

+ 1 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/impl/CustomLockProvider.java

@@ -76,6 +76,7 @@ public class CustomLockProvider implements LockProvider {
         return lock;
     }
 
+    @Override
     public void clear() {
         if (lockMap.isEmpty()) {
             return;

+ 9 - 9
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/impl/ReadWriteLock.java

@@ -121,14 +121,14 @@ public class ReadWriteLock {
         }
     }
 
-    public static void main(String[] args) {
-        ExecutorService executor = Executors.newFixedThreadPool(5);
-        final ReadWriteLock lock = new ReadWriteLock();
-        for (int i = 0; i < 5; i++) {
-            executor.submit(new LockTestThread(i + 1, lock));
-        }
-
-        executor.shutdown();
-    }
+//    public static void main(String[] args) {
+//        ExecutorService executor = Executors.newFixedThreadPool(5);
+//        final ReadWriteLock lock = new ReadWriteLock();
+//        for (int i = 0; i < 5; i++) {
+//            executor.submit(new LockTestThread(i + 1, lock));
+//        }
+//
+//        executor.shutdown();
+//    }
 
 }

+ 1516 - 1516
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkStudentServiceImpl.java

@@ -1,1516 +1,1516 @@
-package com.qmth.teachcloud.mark.service.impl;
-
-import java.awt.*;
-import java.awt.color.ColorSpace;
-import java.awt.image.BufferedImage;
-import java.awt.image.ColorConvertOp;
-import java.io.*;
-import java.math.BigDecimal;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import javax.annotation.Resource;
-import javax.imageio.ImageIO;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletResponse;
-import javax.validation.constraints.NotNull;
-
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.util.FileCopyUtils;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.itextpdf.text.Document;
-import com.itextpdf.text.Image;
-import com.itextpdf.text.PageSize;
-import com.itextpdf.text.Rectangle;
-import com.itextpdf.text.pdf.PdfWriter;
-import com.qmth.boot.core.concurrent.service.ConcurrentService;
-import com.qmth.boot.core.exception.ParameterException;
-import com.qmth.boot.tools.excel.ExcelWriter;
-import com.qmth.boot.tools.excel.enums.ExcelType;
-import com.qmth.boot.tools.io.ZipWriter;
-import com.qmth.teachcloud.common.bean.dto.DataPermissionRule;
-import com.qmth.teachcloud.common.bean.vo.FilePathVo;
-import com.qmth.teachcloud.common.contant.SystemConstant;
-import com.qmth.teachcloud.common.entity.BasicExam;
-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.PageSizeEnum;
-import com.qmth.teachcloud.common.enums.ScanStatus;
-import com.qmth.teachcloud.common.enums.mark.MarkPaperStatus;
-import com.qmth.teachcloud.common.enums.mark.SubjectiveStatus;
-import com.qmth.teachcloud.common.service.BasicRoleDataPermissionService;
-import com.qmth.teachcloud.common.service.FileUploadService;
-import com.qmth.teachcloud.common.service.TeachcloudCommonService;
-import com.qmth.teachcloud.common.util.*;
-import com.qmth.teachcloud.mark.bean.UpdateTimeVo;
-import com.qmth.teachcloud.mark.bean.archivescore.*;
-import com.qmth.teachcloud.mark.bean.omredit.OmrEditDomain;
-import com.qmth.teachcloud.mark.bean.omredit.OmrEditPaper;
-import com.qmth.teachcloud.mark.bean.scananswer.*;
-import com.qmth.teachcloud.mark.bean.scanexaminfo.CheckTask;
-import com.qmth.teachcloud.mark.bean.scanexaminfo.ScanExamCheckInfoVo;
-import com.qmth.teachcloud.mark.bean.scanexaminfo.ScanExamInfoVo;
-import com.qmth.teachcloud.mark.bean.student.AbsentManualUpdateVo;
-import com.qmth.teachcloud.mark.bean.student.StudentQuery;
-import com.qmth.teachcloud.mark.bean.student.StudentVo;
-import com.qmth.teachcloud.mark.dto.UnexistStudentDto;
-import com.qmth.teachcloud.mark.dto.mark.ScoreInfo;
-import com.qmth.teachcloud.mark.dto.mark.ScoreItem;
-import com.qmth.teachcloud.mark.dto.mark.manage.Task;
-import com.qmth.teachcloud.mark.dto.mark.score.*;
-import com.qmth.teachcloud.mark.entity.*;
-import com.qmth.teachcloud.mark.enums.ExamStatus;
-import com.qmth.teachcloud.mark.enums.LockType;
-import com.qmth.teachcloud.mark.enums.OmrTaskStatus;
-import com.qmth.teachcloud.mark.lock.LockService;
-import com.qmth.teachcloud.mark.mapper.MarkStudentMapper;
-import com.qmth.teachcloud.mark.params.MarkHeaderResult;
-import com.qmth.teachcloud.mark.service.*;
-import com.qmth.teachcloud.mark.utils.BatchGetDataUtil;
-import com.qmth.teachcloud.mark.utils.Calculator;
-import com.qmth.teachcloud.mark.utils.ScoreCalculateUtil;
-
-/**
- * <p>
- * 考试考生库 服务实现类
- * </p>
- *
- * @author xf
- * @since 2023-09-22
- */
-@Service
-public class MarkStudentServiceImpl extends ServiceImpl<MarkStudentMapper, MarkStudent> implements MarkStudentService {
-
-    @Autowired
-    private MarkPaperService markPaperService;
-
-    @Resource
-    private MarkPaperPackageService markPaperPackageService;
-
-    @Autowired
-    private ScanPackageService scanPackageService;
-
-    @Autowired
-    private ScanPaperService scanPaperService;
-
-    @Autowired
-    private ScanPaperPageService scanPaperPageService;
-
-    @Autowired
-    private ScanOmrTaskService scanOmrTaskService;
-
-    @Autowired
-    private ScanAnswerCardService answerCardService;
-
-    @Autowired
-    private ScanStudentPaperService studentPaperService;
-
-    @Resource
-    private MarkQuestionService markQuestionService;
-
-    @Resource
-    private TeachcloudCommonService teachcloudCommonService;
-
-    @Autowired
-    private ConcurrentService concurrentService;
-
-    @Resource
-    private MarkService markService;
-
-    @Resource
-    private LockService lockService;
-
-    @Resource
-    private TaskService taskService;
-
-    @Resource
-    private BasicRoleDataPermissionService basicRoleDataPermissionService;
-
-    @Autowired
-    private MarkSubjectiveScoreService markSubjectiveScoreService;
-
-    @Resource
-    private MarkSyncService markSyncService;
-
-    @Resource
-    private FileUploadService fileUploadService;
-    @Resource
-    private MarkTrackService markTrackService;
-
-    @Override
-    public List<String> listClassByExamIdAndCourseCode(Long examId, String paperNumber) {
-        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber);
-        List<MarkStudent> markStudentList = this.list(queryWrapper);
-
-        List<String> classNameList = new ArrayList<>();
-        if (CollectionUtils.isNotEmpty(markStudentList)) {
-            classNameList = markStudentList.stream().filter(m -> StringUtils.isNotBlank(m.getClassName()))
-                    .map(MarkStudent::getClassName).distinct().collect(Collectors.toList());
-        }
-        return classNameList;
-    }
-
-    @Override
-    public void updateSubjectiveStatusAndScore(Long studentId, SubjectiveStatus status, Double score,
-                                               String scoreList) {
-        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkStudent::getSubjectiveStatus, status).set(MarkStudent::getSubjectiveScore, score)
-                .set(MarkStudent::getSubjectiveScoreList, scoreList).eq(MarkStudent::getId, studentId);
-        this.update(updateWrapper);
-    }
-
-    @Override
-    public void updateSubjectiveStatusAndScore(Long examId, String paperNumber, SubjectiveStatus status, Double score,
-                                               String scoreList) {
-        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkStudent::getSubjectiveStatus, status).set(MarkStudent::getSubjectiveScore, score)
-                .set(MarkStudent::getSubjectiveScoreList, scoreList)
-                .set(MarkStudent::getCheckUserId, null)
-                .set(MarkStudent::getCheckTime, null).eq(MarkStudent::getExamId, examId)
-                .eq(MarkStudent::getPaperNumber, paperNumber);
-        this.update(updateWrapper);
-    }
-
-    @Override
-    public ScanExamInfoVo getScanExamInfo(BasicExam exam, String courseCode, String coursePaperId) {
-        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
-        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
-                sysUser.getId(), ServletUtil.getRequest().getServletPath());
-        ScanExamInfoVo vo = new ScanExamInfoVo();
-        vo.setId(exam.getId());
-        vo.setName(exam.getName());
-        vo.getAnswerScan().setCourseCount(
-                markPaperService.getCourseCount(exam.getId(), courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        vo.getAnswerScan().setPaperNumberCount(markPaperService.getPaperNumberCount(exam.getId(), courseCode,
-                coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        vo.getAnswerScan()
-                .setTotalCount(getCount(exam.getId(), null, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        vo.getAnswerScan().setScannedCount(
-                getCount(exam.getId(), ScanStatus.SCANNED, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        vo.getPackageScan().setTotalCount(markPaperPackageService.getPackageCountByExamId(exam.getId(), courseCode,
-                coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        vo.getPackageScan().setScannedCount(
-                scanPackageService.getCount(exam.getId(), courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        return vo;
-    }
-
-    @Override
-    public IPage<StudentScoreDetailDto> pageStudentScore(Long examId, String paperNumber, String college, String majorName,
-                                                         String className, String teacher, Integer filter, String status, Boolean breach, Double startScore,
-                                                         Double endScore, Double subScore, Integer objectiveScoreRateLt, String studentName, String studentCode,
-                                                         String orderType, String orderField, Integer pageNumber, Integer pageSize) {
-        if (startScore != null && endScore == null) {
-            throw ExceptionResultEnum.ERROR.exception("请输入结束分数值");
-        }
-        Page<StudentScoreDetailDto> page = new Page<>(pageNumber, pageSize);
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        Double objectiveScoreLt = objectiveScoreRateLt == null ? null : Calculator.round(Calculator.divide(Calculator.multiply(markPaper.getObjectiveScore(), Double.parseDouble(String.valueOf(objectiveScoreRateLt))), 100), 2);
-        IPage<StudentScoreDetailDto> studentScoreDetailDtoIPage = this.baseMapper.pageStudentScore(page, examId,
-                paperNumber, college, majorName, className, teacher, filter, status, breach, startScore, endScore, subScore,
-                objectiveScoreLt, studentName, studentCode, orderType, orderField);
-        for (StudentScoreDetailDto scoreDetailDto : studentScoreDetailDtoIPage.getRecords()) {
-            // 原图
-            scoreDetailDto.setSheetUrls(buildSheetUrls(scoreDetailDto.getStudentId()));
-            scoreDetailDto.setSheetPath(null);
-            // 状态
-            if (ScanStatus.UNEXIST.equals(scoreDetailDto.getScanStatus())) {
-                scoreDetailDto.setStatusDisplay("未扫描");
-            } else if (ScanStatus.MANUAL_ABSENT.equals(scoreDetailDto.getScanStatus()) || scoreDetailDto.getAbsent() || scoreDetailDto.getOmrAbsent()) {
-                scoreDetailDto.setStatusDisplay("缺考");
-            } else if (!scoreDetailDto.getAbsent() && !scoreDetailDto.getOmrAbsent() && scoreDetailDto.getUpload() && ScanStatus.SCANNED.equals(scoreDetailDto.getScanStatus())) {
-                scoreDetailDto.setStatusDisplay("正常");
-            }
-
-            // 主观题检查标记
-            scoreDetailDto.setSubjectiveCheckFlag(!scoreDetailDto.getAbsent() && !scoreDetailDto.getOmrAbsent() && scoreDetailDto.getUpload() && ScanStatus.SCANNED.equals(scoreDetailDto.getScanStatus()) && SubjectiveStatus.MARKED.equals(scoreDetailDto.getSubjectiveStatus()) && StringUtils.isNotBlank(scoreDetailDto.getSubjectiveScore()) && StringUtils.isNotBlank(scoreDetailDto.getSubjectiveScoreList()));
-            // 客观题检查标记
-            scoreDetailDto.setObjectiveCheckFlag(!scoreDetailDto.getAbsent() && !scoreDetailDto.getOmrAbsent() && scoreDetailDto.getUpload() && ScanStatus.SCANNED.equals(scoreDetailDto.getScanStatus()) && StringUtils.isNotBlank(scoreDetailDto.getObjectiveScore()) && StringUtils.isNotBlank(scoreDetailDto.getObjectiveScoreList()));
-
-            // 格式化分数
-            scoreDetailDto.setObjectiveScore(Calculator.round(scoreDetailDto.getObjectiveScore(), 1));
-            scoreDetailDto.setSubjectiveScore(Calculator.round(scoreDetailDto.getSubjectiveScore(), 1));
-            scoreDetailDto.setTotalScore(Calculator.round(scoreDetailDto.getTotalScore(), 1));
-        }
-        return studentScoreDetailDtoIPage;
-    }
-
-    @Override
-    public List<SheetUrlDto> buildSheetUrls(Long studentId) {
-        // 原图
-        List<SheetUrlDto> sheetUrls = new ArrayList<>();
-        List<StudentPaperDetailDto> studentPaperDetailDtoList = scanPaperService.listStudentPaperDetail(studentId);
-        for (int i = 0; i < studentPaperDetailDtoList.size(); i++) {
-            StudentPaperDetailDto studentPaperDetailDto = studentPaperDetailDtoList.get(i);
-            sheetUrls.add(new SheetUrlDto(
-                    2 * (studentPaperDetailDto.getPaperIndex() - 1) + studentPaperDetailDto.getPageIndex(),
-                    teachcloudCommonService.filePreview(studentPaperDetailDto.getSheetPath()),
-                    studentPaperDetailDto.getRecogData()));
-        }
-        return sheetUrls;
-    }
-
-    private int getCount(Long examId, ScanStatus status, String courseCode, String coursePaperId,
-                         MarkPaperStatus markPaperStatus, DataPermissionRule dpr) {
-        MarkStudent markStudent = new MarkStudent();
-        markStudent.setExamId(examId);
-        markStudent.setCourseCode(courseCode);
-        markStudent.setCoursePaperId(coursePaperId);
-        markStudent.setScanStatus(status);
-        markStudent.setMarkPaperStatus(markPaperStatus.name());
-        return baseMapper.selectCountByQuery(markStudent, dpr);
-    }
-
-    private int getOmrAbsentCount(Long examId, Boolean checked, String courseCode, String coursePaperId,
-                                  MarkPaperStatus status, DataPermissionRule dpr) {
-        MarkStudent markStudent = new MarkStudent();
-        markStudent.setExamId(examId);
-        markStudent.setCourseCode(courseCode);
-        markStudent.setCoursePaperId(coursePaperId);
-        markStudent.setOmrAbsent(true);
-        markStudent.setOmrAbsentChecked(checked);
-        markStudent.setMarkPaperStatus(status.name());
-        return baseMapper.selectCountByQuery(markStudent, dpr);
-    }
-
-    private int getIncompleteCount(Long examId, String courseCode, String coursePaperId, MarkPaperStatus status,
-                                   DataPermissionRule dpr) {
-        MarkStudent markStudent = new MarkStudent();
-        markStudent.setExamId(examId);
-        markStudent.setCourseCode(courseCode);
-        markStudent.setCoursePaperId(coursePaperId);
-        markStudent.setIncomplete(true);
-        markStudent.setMarkPaperStatus(status.name());
-        return baseMapper.selectCountByQuery(markStudent, dpr);
-    }
-
-    @Override
-    public ScanExamCheckInfoVo checkInfo(BasicExam exam, String courseCode, String coursePaperId) {
-        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
-        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
-                sysUser.getId(), ServletUtil.getRequest().getServletPath());
-        Long examId = exam.getId();
-        ScanExamCheckInfoVo vo = new ScanExamCheckInfoVo();
-        vo.setId(exam.getId());
-        vo.setName(exam.getName());
-        CheckTask ct = vo.getCheckTask();
-        ct.setUnexistCount(
-                getCount(examId, ScanStatus.UNEXIST, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        ct.setUnexistCheckedCount(
-                getCount(examId, ScanStatus.MANUAL_ABSENT, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        ct.setAssignedCount(getAssignedCount(examId, false, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        ct.setAssignedCheckedCount(
-                getAssignedCount(examId, true, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        ct.setAbsentCheckCount(
-                getOmrAbsentCount(examId, false, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        ct.setAbsentCheckedCount(
-                getOmrAbsentCount(examId, true, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        ct.setObjectiveCheckCount(scanOmrTaskService.getFinishStudentCountByExamAndUserId(examId, courseCode, coursePaperId,
-                OmrTaskStatus.WAITING.name(), dpr));
-        ct.setObjectiveCheckedCount(scanOmrTaskService.getFinishStudentCountByExamAndUserId(examId, courseCode, coursePaperId,
-                OmrTaskStatus.PROCESSED.name(), dpr));
-        ct.setIncompleteCount(getIncompleteCount(examId, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
-        return vo;
-    }
-
-    /**
-     * 根据考生当前绑定的paper刷新考生状态,需要在外部调用处对考生上锁
-     */
-    @Override
-    @Transactional
-    public void updateStudentByPaper(@NotNull Long userId, @NotNull Long studentId, @NotNull boolean updateOmrTask) {
-        MarkStudent student = this.getById(studentId);
-        if (student == null) {
-            throw new ParameterException("找不到对应的考生");
-        }
-        // 重置状态
-        student.setIncomplete(false);
-        student.setAssigned(false);
-        student.setInvalid(false);
-        student.setQuestionFilled(false);
-        // student.setOmrAbsent(false);
-        int paperCount = 0;
-        List<ScanStudentPaper> studentPaperList = studentPaperService.findByStudentId(studentId);
-        List<String> objectiveAnswers = new ArrayList<>();
-        for (ScanStudentPaper studentPaper : studentPaperList) {
-            paperCount++;
-            // 获取paper详情更新考生状态
-            ScanPaper paper = scanPaperService.getById(studentPaper.getPaperId());
-            student.setAssigned(student.getAssigned() || paper.getAssigned());
-            student.setInvalid(student.getInvalid() || paper.getInvalid());
-            student.setQuestionFilled(student.getQuestionFilled() || paper.getQuestionFilled());
-            student.setCardNumber(paper.getCardNumber());
-            // 单独判断首张纸正面的识别结果
-            if (studentPaper.getPaperIndex() == 1) {
-                // 根据识别结果更新考生属性
-                ScanPaperPage page = scanPaperPageService.findPaperIdAndIndex(paper.getId(), 1);
-                student.setOmrAbsent(page.getAbsent() == null ? false : page.getAbsent().getResult());
-                student.setOmrAbsentChecked(false);
-                if (student.getOmrAbsent()) {
-                    student.setObjectiveScore(null);
-                    student.setObjectiveScoreList(null);
-                }
-                // student.setDevice(batchService.findByPaperId(paper.getId()).getDevice());
-            }
-
-            List<ScanPaperPage> scanPaperPages = scanPaperPageService.listByPaperId(studentPaper.getPaperId());
-            for (ScanPaperPage scanPaperPage : scanPaperPages) {
-                if (scanPaperPage.getQuestion() != null
-                        && CollectionUtils.isNotEmpty(scanPaperPage.getQuestion().getResult())) {
-                    for (String s : scanPaperPage.getQuestion().getResult()) {
-                        if (s.startsWith("?")) {
-                            s = s.replace("?", "");
-                        }
-                        objectiveAnswers.add(s);
-                    }
-                }
-            }
-        }
-        student.setAnswers(JSON.toJSONString(objectiveAnswers));
-        // 更新考生状态
-        if (paperCount > 0) {
-            ScanAnswerCard answerCard = answerCardService.findByExamAndNumber(student.getExamId(),
-                    student.getCardNumber());
-            student.setUpload(true);
-            student.setIncomplete(paperCount != answerCard.getPaperCount());
-            student.setScanStatus(ScanStatus.SCANNED);
-            student.setAbsent(false);
-            student.setManualAbsent(false);
-            // 更新图片数量和图片地址
-            updateStudentSheetInfo(student, studentPaperList);
-        } else {
-            student.setScanStatus(ScanStatus.UNEXIST);
-        }
-        // student.setUpdaterId(userId);
-        // student.setUpdateTime(System.currentTimeMillis());
-        this.saveOrUpdate(student);
-        // 客观题统分
-        this.calculateObjectiveScore(student);
-
-        // 更新课程表中上传人数
-        markPaperService.updateUploadCount(student.getExamId(), student.getPaperNumber(),
-                this.countUploadedByExamIdAndPaperNumber(student.getExamId(), student.getPaperNumber()));
-        markPaperService.updateAbsentCount(student.getExamId(), student.getPaperNumber(),
-                this.countAbsentByExamIdAndPaperNumber(student.getExamId(), student.getPaperNumber()));
-        if (updateOmrTask) {
-            // 清除识别对照任务
-            scanOmrTaskService.deleteByStudentId(student.getExamId(), student.getId());
-            // 重新生成识别对照任务
-            scanOmrTaskService.saveTask(student.getId());
-        }
-    }
-
-    private MarkStudent updateStudentSheetInfo(MarkStudent student, List<ScanStudentPaper> studentPaperList) {
-        List<FilePathVo> filePathVoList = new ArrayList<>();
-        for (ScanStudentPaper scanStudentPaper : studentPaperList) {
-            List<ScanPaperPage> scanPaperPages = scanPaperPageService.listByPaperId(scanStudentPaper.getPaperId());
-            for (ScanPaperPage scanPaperPage : scanPaperPages) {
-                String sheetPath = scanPaperPage.getSheetPath();
-                if (StringUtils.isNotBlank(sheetPath)) {
-                    filePathVoList.add(JSON.parseObject(sheetPath, FilePathVo.class));
-                }
-            }
-        }
-        student.setSheetCount(filePathVoList.size());
-        student.setSheetPath(JSON.toJSONString(filePathVoList));
-        return student;
-    }
-
-    @Override
-    public MarkStudent findByExamIdAndCoursePaperIdAndStudentCode(Long examId, String coursePaperId,
-                                                                  String studentCode) {
-        QueryWrapper<MarkStudent> wrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<MarkStudent> lw = wrapper.lambda();
-        lw.eq(MarkStudent::getExamId, examId);
-        lw.eq(MarkStudent::getCoursePaperId, coursePaperId);
-        lw.eq(MarkStudent::getStudentCode, studentCode);
-        return baseMapper.selectOne(wrapper);
-    }
-
-    @Override
-    public StudentObjectiveDetailDto getObjectiveInspectedTask(Long studentId) {
-        MarkStudent markStudent = this.getById(studentId);
-        StudentObjectiveDetailDto studentObjectiveDetailDto = new StudentObjectiveDetailDto();
-        if (markStudent != null) {
-            studentObjectiveDetailDto.setStudentId(markStudent.getId());
-            studentObjectiveDetailDto.setStudentName(markStudent.getStudentName());
-            studentObjectiveDetailDto.setStudentCode(markStudent.getStudentCode());
-            studentObjectiveDetailDto.setExamPlace(markStudent.getExamPlace());
-            studentObjectiveDetailDto.setExamRoom(markStudent.getExamRoom());
-            studentObjectiveDetailDto.setExamId(markStudent.getExamId());
-            studentObjectiveDetailDto.setCourseCode(markStudent.getCourseCode());
-            studentObjectiveDetailDto.setCourseName(markStudent.getCourseName());
-            studentObjectiveDetailDto.setPaperNumber(markStudent.getPaperNumber());
-            studentObjectiveDetailDto
-                    .setObjectiveScore(markStudent.getObjectiveScore() != null ? markStudent.getObjectiveScore() : 0);
-            studentObjectiveDetailDto.setSubjectiveScore(
-                    markStudent.getSubjectiveScore() != null ? markStudent.getSubjectiveScore() : 0);
-            studentObjectiveDetailDto.setUpload(markStudent.getUpload());
-            studentObjectiveDetailDto.setAbsent(markStudent.getAbsent());
-            studentObjectiveDetailDto.setSheetUrls(this.buildSheetUrls(studentId));
-
-            List<MarkQuestion> questions = markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(
-                    markStudent.getExamId(), markStudent.getPaperNumber(), null, true);
-            List<String> answers = markStudent.getAnswerList();
-            int questionCount = questions.size();
-            int answerCount = answers.size();
-
-            List<StudentObjectiveAnswerDto> answerDtoList = new ArrayList<>();
-            Map<Integer, String> titles = new HashMap<>();
-            // 已设置客观题
-            int maxCount = Math.max(questionCount, answerCount);
-            for (int i = 0; i < maxCount; i++) {
-                MarkQuestion q = questionCount > i ? questions.get(i) : null;
-                String answer = answerCount > i ? answers.get(i) : "#";
-                StudentObjectiveAnswerDto studentObjectiveAnswerDto = new StudentObjectiveAnswerDto();
-                studentObjectiveAnswerDto.setMainNumber(q != null ? q.getMainNumber() : 0);
-                studentObjectiveAnswerDto.setSubNumber(q != null ? q.getSubNumber() : 0);
-                studentObjectiveAnswerDto.setAnswer(answer);
-                studentObjectiveAnswerDto.setExist(q != null && q.getTotalScore() > 0);
-                studentObjectiveAnswerDto.setQuestionType(q.getQuestionType());
-                answerDtoList.add(studentObjectiveAnswerDto);
-
-                if (q != null) {
-                    titles.put(q.getMainNumber(), q.getMainTitle());
-                }
-            }
-            studentObjectiveDetailDto.setAnswers(answerDtoList);
-            studentObjectiveDetailDto.setTitles(titles);
-        }
-
-        return studentObjectiveDetailDto;
-    }
-
-    @Override
-    public Boolean saveObjectiveInspectedTask(Long studentId, String answers) {
-        Long userId = ServletUtil.getRequestUserId();
-        MarkStudent student = this.getById(studentId);
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(student.getExamId(), student.getPaperNumber());
-        // 评卷是否结束
-        if (markPaper == null || MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
-            throw ExceptionResultEnum.ERROR.exception("科目已结束评卷,无法打分");
-        }
-        answers = StringUtils.trimToEmpty(answers);
-        if (student != null) {
-            student.setAnswers(answers.toUpperCase());
-            student.setCheckUserId(userId);
-            student.setCheckTime(System.currentTimeMillis());
-            return saveUploadStudent(student);
-        } else {
-            return false;
-        }
-    }
-
-    @Override
-    public int countUploadedByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
-                .eq(MarkStudent::getUpload, true)
-                .eq(MarkStudent::getScanStatus, ScanStatus.SCANNED)
-                .eq(MarkStudent::getAbsent, false)
-                .eq(MarkStudent::getBreach, false)
-                .eq(MarkStudent::getOmrAbsent, false);
-        return this.count(queryWrapper);
-    }
-
-    @Override
-    public boolean updateScanInfo(MarkStudent student) {
-        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkStudent::getAnswers, student.getAnswers())
-                .set(MarkStudent::getBatchCode, student.getBatchCode()).set(MarkStudent::getAbsent, student.getAbsent())
-                .set(MarkStudent::getUploadTime, System.currentTimeMillis())
-                .set(MarkStudent::getCardNumber, student.getCardNumber()).eq(MarkStudent::getId, student.getId());
-        return this.update(updateWrapper);
-
-    }
-
-    @Override
-    public List<MarkStudent> listAbsentOrBreachMarkTaskStudent(Long examId, String paperNumber) {
-        return this.baseMapper.listAbsentOrBreachMarkTaskStudent(examId, paperNumber);
-    }
-
-    @Override
-    public List<MarkStudent> listUnMarkTaskStudent(Long examId, String paperNumber, Integer groupNumber, int pageSize) {
-        Page<MarkStudent> page = new Page<>(1, pageSize);
-        IPage<MarkStudent> markStudentIPage = this.baseMapper.listUnMarkTaskStudent(page, examId, paperNumber,
-                groupNumber);
-        return markStudentIPage.getRecords();
-    }
-
-    /**
-     * 客观题统分 统分场景: 1.后台-成绩检查-客观题检查 2.扫描端-考生图片上传 3.扫描端-客观题二次识别
-     *
-     * @param student
-     * @return
-     */
-    @Override
-    public boolean saveUploadStudent(MarkStudent student) {
-        MarkStudent old = this.getById(student.getId());
-        if (!student.getAbsent()) {// 正考
-            MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(student.getExamId(),
-                    student.getPaperNumber());
-            if (markPaper.getStatus().equals(MarkPaperStatus.FINISH)) {
-                markPaperService.updateStatus(markPaper.getExamId(), markPaper.getPaperNumber(), MarkPaperStatus.FORMAL,
-                        MarkPaperStatus.FINISH);
-            }
-        }
-        calculateObjectiveScore(student);
-        if (student.getAbsent()) {// 转缺考
-            student.setObjectiveScore(null);
-            student.setObjectiveScoreList(null);
-        }
-        if (!old.getAbsent() && student.getAbsent()) {// 正考转缺考
-            student.setSubjectiveScore(null);
-            student.setSubjectiveScoreList(null);
-            student.setSubjectiveStatus(SubjectiveStatus.UNMARK);
-            this.updateById(student);
-        }
-        boolean success = this.updateScanInfo(student);
-        if (success) {
-            markPaperService.updateUploadCount(student.getExamId(), student.getPaperNumber(),
-                    this.countUploadedByExamIdAndPaperNumber(student.getExamId(), student.getPaperNumber()));
-        }
-        return success;
-    }
-
-    @Override
-    public void calculateObjectiveScore(MarkStudent student) {
-        // 缺考状态不统分(人工指定缺考、识别缺考)
-        if (!ScanStatus.MANUAL_ABSENT.equals(student.getScanStatus()) && !student.getAbsent() && !student.getOmrAbsent()) {
-            ScoreCalculateUtil util = ScoreCalculateUtil.instance(student);
-            ScoreInfo info = util.calculate(markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(
-                    student.getExamId(), student.getPaperNumber(), null, true), null);
-            student.setObjectiveScore(info.getObjectiveScore());
-            student.setScoreList(info.getScoreList(), true);
-            this.updateObjectiveScoreAndScoreList(student);
-        }
-    }
-
-    @Override
-    @Transactional
-    public void updateStudentAndPaper(@NotNull SysUser user, @NotNull Long id,
-                                      @NotNull List<ScanStudentPaper> studentPaperList) {
-        if (CollectionUtils.isNotEmpty(studentPaperList)) {
-            for (ScanStudentPaper studentPaper : studentPaperList) {
-                studentPaper.setStudentId(id);
-            }
-        }
-        // 清空原有绑定关系
-        if (studentPaperService.removeByStudentId(id) > 0) {
-            // 删除评卷任务
-            MarkStudent student = this.getById(id);
-            markService.deleteMarkTaskByStudent(student);
-        }
-        if (CollectionUtils.isNotEmpty(studentPaperList)) {
-            // 保存绑定关系
-            studentPaperService.saveOrUpdateBatch(studentPaperList);
-        }
-        // 更新考生状态
-        updateStudentByPaper(user.getId(), id, true);
-    }
-
-    @Override
-    public StudentVo findOne(StudentQuery query) {
-        return baseMapper.findOne(query);
-    }
-
-    @Override
-    public int countByExamIdAndSecretNumber(Long examId, String secretNumber) {
-        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getSecretNumber, secretNumber);
-        return this.count(queryWrapper);
-    }
-
-    @Override
-    public List<MarkStudent> listByExamIdAndCoursePaperId(Long examId, String coursePaperId) {
-        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getCoursePaperId, coursePaperId);
-        return this.list(queryWrapper);
-    }
-
-    @Override
-    public IPage<AnswerQueryVo> query(AnswerQueryDomain query) {
-        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
-        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
-                sysUser.getId(), ServletUtil.getRequest().getServletPath());
-        // 查询考生分页信息
-        query.setMarkPaperStatus(MarkPaperStatus.FORMAL.name());
-        IPage<AnswerQueryVo> iPage = baseMapper.queryPage(new Page<>(query.getPageNumber(), query.getPageSize()), query,
-                dpr);
-        if (CollectionUtils.isNotEmpty(iPage.getRecords())) {
-            for (AnswerQueryVo vo : iPage.getRecords()) {
-                if (vo.getIsAbsent() != null && vo.getIsAbsent()) {
-                    vo.setExamStatus(ExamStatus.ABSENT);
-                } else {
-                    vo.setExamStatus(ExamStatus.OK);
-                }
-            }
-        }
-        if (CollectionUtils.isNotEmpty(iPage.getRecords()) && (query.getWithPaper() != null && query.getWithPaper())) {
-            Map<Long, AnswerQueryVo> map = new HashMap<>();
-
-            for (AnswerQueryVo vo : iPage.getRecords()) {
-                List<AnswerPaperVo> papers = new ArrayList<>();
-                vo.setPapers(papers);
-                if (vo.getCardPaperCount() != null) {
-                    for (int i = 1; i <= vo.getCardPaperCount(); i++) {
-                        AnswerPaperVo pv = new AnswerPaperVo();
-                        pv.setNumber(i);
-                        papers.add(pv);
-                    }
-                }
-                map.put(vo.getId(), vo);
-            }
-            // 根据考生id查找绑定paper
-            List<Long> studentIds = iPage.getRecords().stream().map(p -> p.getId()).collect(Collectors.toList());
-            List<StudentPaperVo> paperList = new BatchGetDataUtil<StudentPaperVo, Long>() {
-
-                @Override
-                public List<StudentPaperVo> getData(List<Long> paramList) {
-                    return scanPaperService.listByStudentIds(paramList);
-                }
-            }.getDataForBatch(studentIds, 200);
-
-            if (CollectionUtils.isNotEmpty(paperList)) {
-                Map<Long, AnswerPaperVo> paperMap = new HashMap<>();
-                for (StudentPaperVo p : paperList) {
-                    AnswerQueryVo vo = map.get(p.getStudentId());
-                    if (vo == null) {
-                        continue;
-                    }
-                    List<AnswerPaperVo> papers = vo.getPapers();
-                    if (papers == null) {
-                        continue;
-                    }
-                    if (papers.size() < p.getNumber()) {
-                        continue;
-                    }
-                    AnswerPaperVo pvo = papers.get(p.getNumber() - 1);
-                    pvo.setId(p.getPaperId());
-                    pvo.setNumber(p.getNumber());
-                    pvo.setAssigned(p.getAssigned());
-                    pvo.setInvalid(p.getInvalid());
-                    paperMap.put(p.getPaperId(), pvo);
-                }
-                // 查找page
-                List<Long> paperIds = paperList.stream().map(p -> p.getPaperId()).collect(Collectors.toList());
-                List<ScanPaperPage> paperPageList = new BatchGetDataUtil<ScanPaperPage, Long>() {
-
-                    @Override
-                    public List<ScanPaperPage> getData(List<Long> paramList) {
-                        return scanPaperPageService.listByPaperList(paramList);
-                    }
-                }.getDataForBatch(paperIds, 200);
-
-                if (CollectionUtils.isNotEmpty(paperPageList)) {
-                    for (ScanPaperPage p : paperPageList) {
-                        AnswerPaperVo pvo = paperMap.get(p.getPaperId());
-                        if (pvo == null) {
-                            continue;
-                        }
-                        List<AnswerPageVo> pages = pvo.getPages();
-                        if (pages == null) {
-                            pages = new ArrayList<>();
-                            pvo.setPages(pages);
-                        }
-                        AnswerPageVo pageVo = new AnswerPageVo();
-                        pageVo.setIndex(p.getPageIndex());
-                        pageVo.setSheetUri(teachcloudCommonService.filePreview(p.getSheetPath()));
-                        if (query.getWithOmrDetail() != null && query.getWithOmrDetail()) {
-                            pageVo.setAbsent(p.getAbsent() != null ? p.getAbsent().getResult() : null);
-                            pageVo.setBreach(p.getBreach() != null ? p.getBreach().getResult() : null);
-                            pageVo.setQuestion(p.getQuestion() != null ? p.getQuestion().getResult() : null);
-                            pageVo.setRecogData(p.getRecogData());
-                        }
-                        pages.add(pageVo);
-                    }
-                }
-            }
-        }
-        return iPage;
-    }
-
-    @Override
-    public List<String> summary(AnswerQueryDomain query) {
-        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
-        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
-                sysUser.getId(), ServletUtil.getRequest().getServletPath());
-
-        // 不分页查询考生准考证号
-        query.setMarkPaperStatus(MarkPaperStatus.FORMAL.name());
-        return baseMapper.querySummary(query, dpr);
-    }
-
-    @Transactional
-    @Override
-    public UpdateTimeVo omrEdit(Long userId, OmrEditDomain domain) {
-        MarkStudent student = findByExamIdAndCoursePaperIdAndStudentCode(domain.getExamId(), domain.getCoursePaperId(),
-                domain.getStudentCode());
-        if (student == null) {
-            throw new ParameterException("考生信息未找到");
-        }
-        concurrentService.getReadWriteLock(LockType.STUDENT + "-" + student.getId()).writeLock().lock();
-        try {
-            for (OmrEditPaper paperEdit : domain.getPapers()) {
-                ScanStudentPaper sp = studentPaperService.findByStudentIdAndPaperNumber(student.getId(),
-                        paperEdit.getNumber());
-                if (sp == null) {
-                    throw new ParameterException("未找到绑定扫描结果");
-                }
-                ScanPaper paperEntity = scanPaperService.getById(sp.getPaperId());
-                if (paperEntity == null) {
-                    throw new ParameterException("未找到paper信息结果");
-                }
-                paperEntity.setUpdaterId(userId);
-                paperEntity.setUpdateTime(System.currentTimeMillis());
-                List<ScanPaperPage> pages = scanPaperPageService.listByPaperId(paperEntity.getId());
-                for (ScanPaperPage pageEntity : pages) {
-                    paperEdit.updatePage(pageEntity);
-                }
-                scanPaperService.savePaperAndPages(paperEntity, pages);
-            }
-            updateStudentByPaper(userId, student.getId(), true);
-            return UpdateTimeVo.create();
-        } finally {
-            concurrentService.getReadWriteLock(LockType.STUDENT + "-" + student.getId()).writeLock().unlock();
-        }
-    }
-
-    @Override
-    public long countByExamIdAndPaperNumber(Long examId, String paperNumber, String paperType) {
-        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
-                .eq(MarkStudent::getPaperType, paperType);
-        return this.count(queryWrapper);
-    }
-
-    @Transactional
-    @Override
-    public AbsentManualUpdateVo absentManualUpdate(Long examId, String coursePaperId, String studentCode,
-                                                   ScanStatus status) {
-        MarkStudent student = findByExamIdAndCoursePaperIdAndStudentCode(examId, coursePaperId, studentCode);
-        if (student == null) {
-            throw new ParameterException("考生未找到");
-        }
-        if (ScanStatus.MANUAL_ABSENT.equals(status) && !ScanStatus.UNEXIST.equals(student.getScanStatus())) {
-            throw new ParameterException("考生不是未扫描状态");
-        }
-        LambdaUpdateWrapper<MarkStudent> lw = new LambdaUpdateWrapper<>();
-        lw.set(MarkStudent::getScanStatus, status);
-        lw.set(MarkStudent::getManualAbsent, ScanStatus.MANUAL_ABSENT.equals(status));
-        lw.set(MarkStudent::getAbsent, ScanStatus.MANUAL_ABSENT.equals(status));
-        lw.eq(MarkStudent::getId, student.getId());
-        update(lw);
-        markPaperService.updateAbsentCount(examId, student.getPaperNumber(),
-                this.countAbsentByExamIdAndPaperNumber(examId, student.getPaperNumber()));
-        return AbsentManualUpdateVo.create(status);
-    }
-
-    @Transactional
-    @Override
-    public UpdateTimeVo confirm(Long examId, String coursePaperId, String studentCode, Boolean omrAbsent) {
-        MarkStudent student = findByExamIdAndCoursePaperIdAndStudentCode(examId, coursePaperId, studentCode);
-        LambdaUpdateWrapper<MarkStudent> lw = new LambdaUpdateWrapper<>();
-        lw.set(MarkStudent::getOmrAbsentChecked, omrAbsent);
-        if (omrAbsent) {
-            lw.set(MarkStudent::getObjectiveScore, null);
-            lw.set(MarkStudent::getObjectiveScoreList, null);
-        } else {
-            lw.set(MarkStudent::getOmrAbsent, false);
-        }
-        lw.eq(MarkStudent::getId, student.getId());
-        update(lw);
-        scanOmrTaskService.deleteByStudentId(student.getExamId(), student.getId());
-        if (!omrAbsent) {
-            calculateObjectiveScore(student);
-            scanOmrTaskService.saveTask(student.getId());
-        }
-        markPaperService.updateAbsentCount(examId, student.getPaperNumber(),
-                this.countAbsentByExamIdAndPaperNumber(examId, student.getPaperNumber()));
-        return UpdateTimeVo.create();
-    }
-
-    @Override
-    public List<Long> findIdByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        return this.baseMapper.findIdByExamIdAndPaperNumber(examId, paperNumber);
-    }
-
-    @Override
-    public Task getSubjectiveInspectedTask(Long studentId) {
-        Task task = null;
-        if (studentId != null) {
-            Long userId = ServletUtil.getRequestUserId();
-            MarkStudent markStudent = this.getById(studentId);
-            markService.releaseByStudent(markStudent);
-            if (markService.applyStudent(markStudent, userId)) {
-                task = taskService.build(studentId);
-            }
-        }
-        return task;
-    }
-
-    @Transactional
-    @Override
-    public void saveSubjectiveInspectedTask(MarkHeaderResult markResult) {
-        MarkStudent markStudent = this.getById(markResult.getStudentId());
-        if (markStudent == null) {
-            throw ExceptionResultEnum.ERROR.exception("考生不存在");
-        }
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markStudent.getExamId(),
-                markStudent.getPaperNumber());
-        // 评卷是否结束
-        if (markPaper == null || MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
-            throw ExceptionResultEnum.ERROR.exception("科目已结束评卷,无法打分");
-        }
-        try {
-            lockService.watch(LockType.EXAM_SUBJECT, markStudent.getExamId(), markStudent.getPaperNumber());
-            lockService.waitlock(LockType.STUDENT, markResult.getStudentId());
-            markService.submitHeaderTask(markResult.getGroups(), markStudent);
-        } catch (Exception e) {
-            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
-        } finally {
-            lockService.unlock(LockType.STUDENT, markResult.getStudentId());
-            lockService.unwatch(LockType.EXAM_SUBJECT, markStudent.getExamId(), markStudent.getPaperNumber());
-            markService.releaseByStudent(markStudent);
-        }
-    }
-
-    @Override
-    public IPage<ArchiveStudentVo> studentList(ArchiveStudentQuery query) {
-        Page<ArchiveStudentVo> page = new Page<>(query.getPageNumber(), query.getPageSize());
-        IPage<ArchiveStudentVo> ret = baseMapper.studentList(page, query);
-        for (ArchiveStudentVo record : ret.getRecords()) {
-            List<String> list = new ArrayList<>();
-            List<FilePathVo> vos = JSON.parseArray(StringUtils.trimToNull(record.getSheetPath()), FilePathVo.class);
-            if (CollectionUtils.isEmpty(vos)) {
-                continue;
-            }
-            for (FilePathVo filePathVo : vos) {
-                list.add(JSON.toJSONString(filePathVo));
-            }
-            record.setSheetUrls(teachcloudCommonService.filePreview(list));
-        }
-        return ret;
-    }
-
-    @Override
-    public void scoreExport(ArchiveStudentQuery query, HttpServletResponse response) {
-        //生成表头
-        String[] columnName = new String[]{"学生姓名", "学号", "学院", "班级", "课程代码", "课程名称", "客观分", "主观分",
-                "成绩"};
-        List<MarkQuestion> oQuestionList = markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(query.getExamId(), query.getPaperNumber(), null, true);
-        List<MarkQuestion> sQuestionList = markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(query.getExamId(), query.getPaperNumber(), null, false);
-        List<String> columnNameList = new ArrayList<>(Arrays.asList(columnName));
-        for (MarkQuestion question : oQuestionList) {
-            columnNameList.add(question.getMainTitle() + " " + question.getMainNumber() + "-" + question.getSubNumber() + "选项");
-            columnNameList.add(question.getMainTitle() + " " + question.getMainNumber() + "-" + question.getSubNumber() + "得分");
-        }
-        for (MarkQuestion question : oQuestionList) {
-            columnNameList.add(question.getMainTitle() + " " + question.getMainNumber() + "-" + question.getSubNumber());
-        }
-        String[] columnNames = columnNameList.toArray(new String[0]);
-        //生成动态内容
-        List<String[]> columnValues = new ArrayList<>();
-        List<ArchiveStudentVo> ret = baseMapper.studentList(query);
-        for (ArchiveStudentVo s : ret) {
-            List<String> valueList = new ArrayList<>();
-            valueList.add(s.getStudentName());
-            valueList.add(s.getStudentCode());
-            valueList.add(s.getCollege());
-            valueList.add(s.getClassName());
-            valueList.add(s.getCourseCode());
-            valueList.add(s.getCourseName());
-            valueList.add(s.getObjectiveScore() == null ? "" : s.getObjectiveScore().toString());
-            valueList.add(s.getSubjectiveScore() == null ? "" : s.getSubjectiveScore().toString());
-            valueList.add(s.getTotalScore() == null ? "" : s.getTotalScore().toString());
-            for (ScoreItem item : s.getScoreList(true, oQuestionList)) {
-                valueList.add(item.getAnswer());
-                valueList.add(item.getScore() == null ? "" : item.getScore().toString());
-            }
-            for (ScoreItem item : s.getScoreList(false, sQuestionList)) {
-                valueList.add(item.getScore().toString());
-            }
-            String[] columnValue = valueList.toArray(new String[valueList.size()]);
-            columnValues.add(columnValue);
-        }
-        try {
-            log.debug("导出Excel开始...");
-            response.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode("成绩导出", SystemConstant.CHARSET_NAME) + ".xlsx");
-            response.setContentType("application/vnd.ms-excel");
-            ServletOutputStream outputStream = response.getOutputStream();
-            ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
-            writer.writeDataArrays("成绩导出", null, columnNames, columnValues.listIterator());
-            writer.output(outputStream);
-            outputStream.flush();
-            outputStream.close();
-            log.debug("导出Excel结束");
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Override
-    public ScoreReportVo scoreReport(ArchiveStudentQuery query) {
-        ScoreReportVo ret = new ScoreReportVo();
-        ret.setOverview(baseMapper.overview(query));
-        if (ret.getOverview() != null) {
-            double total = ret.getOverview().getStudentCount() - ret.getOverview().getAbsentCount();
-            ret.getOverview().setPassRate(
-                    Calculator.divide2String(Calculator.multiply(ret.getOverview().getPassCount(), 100), total, 2));
-            ret.getOverview().setExcellentRate(Calculator
-                    .divide2String(Calculator.multiply(ret.getOverview().getExcellentCount(), 100), total, 2));
-            ret.getOverview().setAvgScore(Calculator.round(ret.getOverview().getAvgScore(), 2));
-        }
-        List<ArchiveStudentVo> studentList = baseMapper.studentList(query);
-        fillScoreRange(ret, studentList);
-
-        ret.setCollege(baseMapper.college(query));
-        if (CollectionUtils.isNotEmpty(ret.getCollege())) {
-            for (CollegeVo vo : ret.getCollege()) {
-                double total = vo.getStudentCount() - vo.getAbsentCount();
-                vo.setPassRate(Calculator.divide2String(Calculator.multiply(vo.getPassCount(), 100), total, 2));
-                vo.setExcellentRate(
-                        Calculator.divide2String(Calculator.multiply(vo.getExcellentCount(), 100), total, 2));
-                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
-                vo.setMinScore(Calculator.round(vo.getMinScore(), 2));
-                vo.setMaxScore(Calculator.round(vo.getMaxScore(), 2));
-            }
-        }
-
-        ret.setClassData(baseMapper.classData(query));
-        if (CollectionUtils.isNotEmpty(ret.getClassData())) {
-            for (ClassVo vo : ret.getClassData()) {
-                double total = vo.getStudentCount() - vo.getAbsentCount();
-                vo.setPassRate(Calculator.divide2String(Calculator.multiply(vo.getPassCount(), 100), total, 2));
-                vo.setExcellentRate(
-                        Calculator.divide2String(Calculator.multiply(vo.getExcellentCount(), 100), total, 2));
-                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
-                vo.setMinScore(Calculator.round(vo.getMinScore(), 2));
-                vo.setMaxScore(Calculator.round(vo.getMaxScore(), 2));
-            }
-        }
-
-        ret.setTeacher(baseMapper.teacher(query));
-        if (CollectionUtils.isNotEmpty(ret.getTeacher())) {
-            for (TeacherVo vo : ret.getTeacher()) {
-                double total = vo.getStudentCount() - vo.getAbsentCount();
-                vo.setPassRate(Calculator.divide2String(Calculator.multiply(vo.getPassCount(), 100), total, 2));
-                vo.setExcellentRate(
-                        Calculator.divide2String(Calculator.multiply(vo.getExcellentCount(), 100), total, 2));
-                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
-                vo.setMinScore(Calculator.round(vo.getMinScore(), 2));
-                vo.setMaxScore(Calculator.round(vo.getMaxScore(), 2));
-            }
-        }
-
-        ret.setTeacherClass(baseMapper.teacherClass(query));
-        if (CollectionUtils.isNotEmpty(ret.getTeacherClass())) {
-            for (TeacherClassVo vo : ret.getTeacherClass()) {
-                double total = vo.getStudentCount() - vo.getAbsentCount();
-                vo.setPassRate(Calculator.divide2String(Calculator.multiply(vo.getPassCount(), 100), total, 2));
-                vo.setExcellentRate(
-                        Calculator.divide2String(Calculator.multiply(vo.getExcellentCount(), 100), total, 2));
-                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
-            }
-        }
-
-        fillObjective(ret, studentList, query.getExamId(), query.getPaperNumber());
-        List<Long> studentIds = studentList.stream().map(ArchiveStudentVo::getStudentId).collect(Collectors.toList());
-        ret.setSubjective(markSubjectiveScoreService.getSubjectiveVo(studentIds));
-        if (CollectionUtils.isNotEmpty(ret.getSubjective())) {
-            for (QuestionVo vo : ret.getSubjective()) {
-                double total = vo.getStudentCount();
-                vo.setScoreRate(Calculator.divide(vo.getScoreCount(), total, 2));
-                vo.setFullScoreRate(Calculator.divide(vo.getFullScoreCount(), total, 2));
-                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
-            }
-        }
-        return ret;
-    }
-
-    @Override
-    public void exportUnexist(Long examId, String courseCode, String coursePaperId, HttpServletResponse response) {
-        try {
-            SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
-            DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
-                    sysUser.getId(), ServletUtil.getRequest().getServletPath());
-            List<UnexistStudentDto> unexistStudentDtoList = this.baseMapper.listUnexistStudentByExamIdAndCoursePaperId(
-                    examId, courseCode, coursePaperId, MarkPaperStatus.FORMAL.name(), dpr);
-            ExcelUtil.excelExport("评卷员工作量", UnexistStudentDto.class, unexistStudentDtoList, response);
-        } catch (Exception e) {
-            throw ExceptionResultEnum.ERROR.exception("导出评卷员工作量失败");
-        }
-    }
-
-    @Override
-    public int countByExamIdAndPaperNumberAndMarkStatus(Long examId, String paperNumber, SubjectiveStatus status) {
-        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<MarkStudent> lambdaQueryWrapper = queryWrapper.lambda();
-        lambdaQueryWrapper.eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber);
-        if (status != null) {
-            lambdaQueryWrapper.eq(MarkStudent::getSubjectiveStatus, status);
-        }
-        lambdaQueryWrapper.eq(MarkStudent::getUpload, true).eq(MarkStudent::getAbsent, false)
-                .eq(MarkStudent::getBreach, false).eq(MarkStudent::getOmrAbsent, false);
-        return this.count(queryWrapper);
-    }
-
-    @Override
-    public void updateCheckInfo(Long studentId, Long userId) {
-        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkStudent::getCheckUserId, userId)
-                .set(MarkStudent::getCheckTime, System.currentTimeMillis()).eq(MarkStudent::getId, studentId);
-        this.update(updateWrapper);
-    }
-
-    @Override
-    public int countOmrAbsentStudent(Long examId, String paperNumber, String paperType, boolean isOmrAbsentConfirm) {
-        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
-                .eq(MarkStudent::getPaperType, paperType).eq(MarkStudent::getOmrAbsent, true)
-                .eq(MarkStudent::getOmrAbsentChecked, isOmrAbsentConfirm);
-        return this.count(queryWrapper);
-    }
-
-    @Override
-    public void scoreReportDownload(JSONObject jsonObject, HttpServletResponse response) {
-        String rootPath = null;
-        File htmlFile;
-        File pdfFile;
-        try {
-            // 本地保存目录
-            File tempFile = SystemConstant.getFileTempDirVar(
-                    System.currentTimeMillis() + File.separator + SystemConstant.getNanoId(),
-                    SystemConstant.TEMP_PREFIX);
-            rootPath = tempFile.getParent();
-
-            // 删除临时目录中创建的临时文件
-            if (Objects.nonNull(tempFile)) {
-                tempFile.delete();
-            }
-            // html文件
-            String cardHtmlPath = rootPath + File.separator + System.currentTimeMillis() + File.separator + "temp"
-                    + SystemConstant.HTML_PREFIX;
-            htmlFile = new File(cardHtmlPath);
-            if (!htmlFile.exists()) {
-                htmlFile.getParentFile().mkdirs();
-                htmlFile.createNewFile();
-            }
-            // 生成html文件
-            FileCopyUtils.copy(jsonObject.getString("htmlContent").getBytes(StandardCharsets.UTF_8), htmlFile);
-
-            // pdf文件
-            String cardPdfPath = rootPath + File.separator + System.currentTimeMillis() + File.separator + "temp"
-                    + SystemConstant.PDF_PREFIX;
-            pdfFile = new File(cardPdfPath);
-            if (!pdfFile.exists()) {
-                pdfFile.getParentFile().mkdirs();
-                pdfFile.createNewFile();
-            }
-            HtmlToPdfUtil.convert(cardHtmlPath, cardPdfPath, PageSizeEnum.A4);
-            FileUtil.outputFile(response, pdfFile, "报告" + SystemConstant.PDF_PREFIX);
-        } catch (Exception e) {
-            e.printStackTrace();
-        } finally {
-            if (Objects.nonNull(rootPath)) {
-                ConvertUtil.delFolder(rootPath);
-            }
-        }
-    }
-
-    @Override
-    public void deleteByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber);
-        this.remove(updateWrapper);
-    }
-
-    @Override
-    public boolean calcObjectiveScore(Long examId, String paperNumber) {
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        if (markPaper != null) {
-            if (lockService.trylock(LockType.SCORE_CALCULATE, markPaper.getId())) {
-                markSyncService.calcObjectiveScore(markPaper);
-                return true;
-            } else {
-                throw ExceptionResultEnum.ERROR.exception("评卷员正在重置");
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public IPage<MarkStudent> pageByExamAndPaperNumber(Long examId, String paperNumber, int pageNumber, int pageSize) {
-        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
-                .orderByAsc(MarkStudent::getId);
-        return this.page(new Page<>(pageNumber, pageSize), queryWrapper);
-    }
-
-    @Override
-    public void updateObjectiveScoreAndScoreList(MarkStudent markStudent) {
-        UpdateWrapper<MarkStudent> objectiveUpdateWrapper = new UpdateWrapper<>();
-        LambdaUpdateWrapper<MarkStudent> lambdaUpdateWrapper = objectiveUpdateWrapper.lambda();
-        lambdaUpdateWrapper.set(MarkStudent::getObjectiveScore, markStudent.getObjectiveScore())
-                .set(MarkStudent::getObjectiveScoreList, markStudent.getObjectiveScoreList());
-        if (markStudent.getCheckUserId() != null) {
-            lambdaUpdateWrapper.set(MarkStudent::getCheckUserId, markStudent.getCheckUserId());
-        }
-        if (markStudent.getCheckTime() != null) {
-            lambdaUpdateWrapper.set(MarkStudent::getCheckTime, markStudent.getCheckTime());
-        }
-        lambdaUpdateWrapper.eq(MarkStudent::getId, markStudent.getId());
-        this.update(objectiveUpdateWrapper);
-    }
-
-    @Override
-    public boolean updateAssignConfirm(Long studentId, boolean assignConfirm) {
-        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkStudent::getAssignConfirmed, assignConfirm).eq(MarkStudent::getId, studentId);
-        return this.update(updateWrapper);
-    }
-
-    private void fillObjective(ScoreReportVo ret, List<ArchiveStudentVo> studentList, Long examId, String paperNumber) {
-        List<MarkQuestion> qs = markQuestionService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, null, true);
-        Map<String, QuestionVo> map = new HashMap<>();
-        List<QuestionVo> list = new ArrayList<>();
-        for (ArchiveStudentVo s : studentList) {
-            List<ScoreItem> sis = s.getScoreList(true, qs);
-            if (CollectionUtils.isNotEmpty(sis)) {
-                for (ScoreItem si : sis) {
-                    String key = si.getMainNumber() + "-" + si.getSubNumber();
-                    QuestionVo vo = map.get(key);
-                    if (vo == null) {
-                        vo = new QuestionVo();
-                        vo.setScoreCount(0);
-                        vo.setFullScoreCount(0);
-                        vo.setStudentCount(0);
-                        vo.setScoreSum(0.0);
-                        vo.setMainNumber(si.getMainNumber());
-                        vo.setSubNumber(si.getSubNumber());
-                        vo.setTitle(si.getTitle());
-                        vo.setScore(si.getTotalScore());
-                        map.put(key, vo);
-                    }
-                    vo.setStudentCount(vo.getStudentCount() + 1);
-                    vo.setScoreSum(vo.getScoreSum() + si.getScore());
-                    if (si.getScore() == si.getTotalScore()) {
-                        vo.setFullScoreCount(vo.getFullScoreCount() + 1);
-                    }
-                    if (si.getScore() > 0) {
-                        vo.setScoreCount(vo.getScoreCount() + 1);
-                    }
-                }
-            }
-        }
-        if (map.isEmpty()) {
-            ret.setObjective(list);
-            return;
-        }
-
-        list = new ArrayList<>(map.values());
-        for (QuestionVo questionVo : list) {
-            // 平均分
-            if (questionVo.getStudentCount() == null || questionVo.getStudentCount() == 0) {
-                questionVo.setAvgScore(0D);
-            } else {
-                questionVo.setAvgScore(Calculator.round(Calculator.divide(questionVo.getScoreSum(), questionVo.getStudentCount()), 2));
-            }
-        }
-
-        Collections.sort(list, (o1, o2) -> {
-            if (o1.getMainNumber() > o2.getMainNumber()) {
-                return 1;
-            } else if (o1.getSubNumber() < o2.getSubNumber()) {
-                return -1;
-            } else {
-                return 0;
-            }
-        });
-        for (QuestionVo vo : list) {
-            double total = vo.getStudentCount();
-            vo.setScoreRate(Calculator.divide(vo.getScoreCount(), total, 2));
-            vo.setFullScoreRate(Calculator.divide(vo.getFullScoreCount(), total, 2));
-        }
-        ret.setObjective(list);
-    }
-
-    private List<MarkStudent> listByExamIdAndPaperNumberAndNotAbsent(Long examId, String paperNumber) {
-        QueryWrapper<MarkStudent> wrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<MarkStudent> lw = wrapper.lambda();
-        lw.eq(MarkStudent::getExamId, examId);
-        lw.eq(MarkStudent::getPaperNumber, paperNumber);
-        lw.eq(MarkStudent::getUpload, true);
-        lw.eq(MarkStudent::getAbsent, false);
-        lw.eq(MarkStudent::getBreach, false);
-        lw.eq(MarkStudent::getOmrAbsent, false);
-
-        return this.list(wrapper);
-    }
-
-    private void fillScoreRange(ScoreReportVo ret, List<ArchiveStudentVo> list) {
-        List<ScoreRangeVo> scoreRange = new ArrayList<>();
-        ret.setScoreRange(scoreRange);
-        scoreRange.add(getScoreRangeVo(list, 1.0, 9.5));
-        scoreRange.add(getScoreRangeVo(list, 10.0, 19.5));
-        scoreRange.add(getScoreRangeVo(list, 20.0, 29.5));
-        scoreRange.add(getScoreRangeVo(list, 30.0, 39.5));
-        scoreRange.add(getScoreRangeVo(list, 40.0, 49.5));
-        scoreRange.add(getScoreRangeVo(list, 50.0, 59.5));
-        scoreRange.add(getScoreRangeVo(list, 60.0, 69.5));
-        scoreRange.add(getScoreRangeVo(list, 70.0, 79.5));
-        scoreRange.add(getScoreRangeVo(list, 80.0, 89.5));
-        scoreRange.add(getScoreRangeVo(list, 90.0, 100.0));
-    }
-
-    private ScoreRangeVo getScoreRangeVo(List<ArchiveStudentVo> list, Double start, Double end) {
-        int count = (int) list.stream().filter(s -> s.getTotalScore() >= start && s.getTotalScore() <= end).count();
-        Double rate = null;
-        if (list.size() != 0) {
-            rate = Calculator.multiply(count, list.size(), 2);
-        }
-        ScoreRangeVo vo = new ScoreRangeVo(count, start, end, rate);
-        return vo;
-    }
-
-    private int getCountByPaperNumber(Long examId, String paperNumber) {
-        QueryWrapper<MarkStudent> wrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<MarkStudent> lw = wrapper.lambda();
-        lw.eq(MarkStudent::getExamId, examId);
-        lw.eq(MarkStudent::getPaperNumber, paperNumber);
-        return this.count(wrapper);
-    }
-
-    @Override
-    public int getAssignedCount(Long examId, Boolean checked, String courseCode, String coursePaperId,
-                                MarkPaperStatus status, DataPermissionRule dpr) {
-        MarkStudent markStudent = new MarkStudent();
-        markStudent.setExamId(examId);
-        markStudent.setCourseCode(courseCode);
-        markStudent.setCoursePaperId(coursePaperId);
-        markStudent.setAssigned(true);
-        markStudent.setAssignConfirmed(checked);
-        markStudent.setMarkPaperStatus(status.name());
-        return baseMapper.countAssigned(markStudent, dpr);
-    }
-
-    @Override
-    public int countAbsentByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
-                .and(o -> o.eq(MarkStudent::getAbsent, true).or().eq(MarkStudent::getOmrAbsent, true));
-        return this.count(queryWrapper);
-    }
-
-    @Override
-    public List<MarkStudent> listScanCollegeByExamIdAndCourseCodeAndCoursePaperId(Long examId, String courseCode,
-                                                                                  String coursePaperId, String status, DataPermissionRule dpr) {
-        return this.baseMapper.listScanCollegeByExamIdAndCourseCodeAndCoursePaperId(examId, courseCode, coursePaperId,
-                status, dpr);
-    }
-
-    @Override
-    public int countUnexistByExamIdAndPaperNumberAndPaperType(Long examId, String paperNumber, String paperType) {
-        MarkStudent markStudent = new MarkStudent();
-        markStudent.setExamId(examId);
-        markStudent.setPaperNumber(paperNumber);
-        markStudent.setScanStatus(ScanStatus.UNEXIST);
-        markStudent.setPaperType(paperType);
-        markStudent.setMarkPaperStatus(MarkPaperStatus.FORMAL.name());
-        return baseMapper.selectCountByQuery(markStudent, null);
-    }
-
-    @Override
-    public void updateStudentAnswer(Long studentId) {
-        List<String> objectiveAnswers = new ArrayList<>();
-        MarkStudent student = this.getById(studentId);
-        List<ScanStudentPaper> studentPaperList = studentPaperService.findByStudentId(studentId);
-        for (ScanStudentPaper studentPaper : studentPaperList) {
-            List<ScanPaperPage> scanPaperPages = scanPaperPageService.listByPaperId(studentPaper.getPaperId());
-            for (ScanPaperPage scanPaperPage : scanPaperPages) {
-                if (scanPaperPage.getQuestion() != null
-                        && CollectionUtils.isNotEmpty(scanPaperPage.getQuestion().getResult())) {
-                    for (String s : scanPaperPage.getQuestion().getResult()) {
-                        if (s.startsWith("?")) {
-                            s = s.replace("?", "");
-                        }
-                        objectiveAnswers.add(s);
-                    }
-                }
-            }
-        }
-        student.setAnswers(JSON.toJSONString(objectiveAnswers));
-        this.saveOrUpdate(student);
-        // 客观题统分
-        this.calculateObjectiveScore(student);
-    }
-
-    @Override
-    public List<MarkStudentScoreVo> listMarkStudentScoreList(Long examId, String paperNumber) {
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        if (!MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
-            throw ExceptionResultEnum.ERROR.exception("科目未结束评卷,不能同步考生成绩");
-        }
-
-        List<MarkStudent> markStudentList = this.listByExamIdAndPaperNumberAndNotAbsent(examId, paperNumber);
-        List<MarkQuestion> objectiveQuestionList = markQuestionService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, null, true);
-        List<MarkQuestion> subjectiveQuestionList = markQuestionService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, null, false);
-        List<MarkStudentScoreVo> markStudentScoreVoList = new ArrayList<>();
-        for (MarkStudent markStudent : markStudentList) {
-            MarkStudentScoreVo markStudentScoreVo = new MarkStudentScoreVo();
-            markStudentScoreVo.setExamId(markStudent.getExamId());
-            markStudentScoreVo.setCourseCode(markStudent.getCourseCode());
-            markStudentScoreVo.setCourseName(markStudent.getCourseName());
-            markStudentScoreVo.setPaperNumber(markStudent.getPaperNumber());
-            markStudentScoreVo.setStudentCode(markStudent.getStudentCode());
-            markStudentScoreVo.setStudentName(markStudent.getStudentName());
-            // 客观题
-            markStudentScoreVo.setObjectiveScore(markStudent.getObjectiveScore());
-            markStudentScoreVo.setObjectiveScoreList(markStudent.getScoreList(true, objectiveQuestionList));
-            // 主观题
-            markStudentScoreVo.setSubjectiveScore(markStudent.getSubjectiveScore());
-            markStudentScoreVo.setSubjectiveScoreList(markStudent.getScoreList(false, subjectiveQuestionList));
-
-            // 总分
-            markStudentScoreVo.setTotalScore(markStudent.getTotalScore());
-            List<ScoreItem> totalScoreItemList = new ArrayList<>();
-            totalScoreItemList.addAll(markStudentScoreVo.getObjectiveScoreList());
-            totalScoreItemList.addAll(markStudentScoreVo.getSubjectiveScoreList());
-            totalScoreItemList.stream().sorted(Comparator.comparing(ScoreItem::getMainNumber).thenComparing(ScoreItem::getSubNumber));
-            markStudentScoreVo.setTotalScoreList(totalScoreItemList);
-            markStudentScoreVoList.add(markStudentScoreVo);
-        }
-        return markStudentScoreVoList;
-    }
-
-    @Override
-    public void trackExport(Long examId, String coursePaperId, HttpServletResponse response) {
-        {
-            List<MarkStudent> list = this.listByExamIdAndCoursePaperId(examId,coursePaperId);
-            if (CollectionUtils.isNotEmpty(list)) {
-                try {
-                    response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("轨迹图.zip","UTF-8"));
-                    ZipWriter writer = ZipWriter.create(response.getOutputStream());
-                    ByteArrayOutputStream os = new ByteArrayOutputStream();
-                    for (MarkStudent s:list) {
-                        if(!s.getUpload()){
-                            continue;
-                        }
-                        List<FilePathVo> vos = JSON.parseArray(s.getSheetPath(), FilePathVo.class);
-                        List<MarkTrack> trackList = markTrackService.listByStudentId(s.getId());
-                        Document document = new Document(new Rectangle(PageSize.A3.getHeight(),PageSize.A3.getWidth()));
-                        // 本地保存目录
-                        File tempFile = SystemConstant.getFileTempParentDirVar(SystemConstant.TEMP_PREFIX);
-                        File file =new File(tempFile.getPath()+File.separator + s.getStudentCode()+".pdf");
-                        PdfWriter pdfWriter = PdfWriter.getInstance(document,new FileOutputStream(file));
-                        document.open();
-                        for (int i = 0; i < vos.size(); i++) {
-                            FilePathVo vo = vos.get(i);
-                            File sheet =  fileUploadService.downloadFile(JSON.toJSONString(vo),vo.getPath());
-                            File track = new File(tempFile.getPath()+File.separator + s.getStudentCode()+"-"+(i+1)+".jpg");
-                            int offsetIndex = i+1;
-                            List<MarkTrack> tracks = trackList.stream().filter(t->t.getOffsetIndex().equals(offsetIndex)).collect(Collectors.toList());
-                            this.createTrack(sheet,track,tracks);
-                            Image image = Image.getInstance(track.getPath());
-                            image.scaleAbsolute(PageSize.A3.getHeight()-100,PageSize.A3.getWidth()-100);
-                            if(i!=0){
-                                document.newPage();
-                            }
-                            document.add(image);
-                            sheet.delete();
-                            track.delete();
-                        }
-                        document.close();
-                        pdfWriter.close();
-                        writer.write(file,s.getStudentCode()+".pdf");
-                    }
-                    writer.close();
-                }catch (Exception e){
-                    e.printStackTrace();
-                    throw new ParameterException("文件下载失败", e);
-                }
-            }
-        }
-    }
-
-    private void createTrack(File sheet, File track, List<MarkTrack> trackList)throws IOException {
-        {
-            FileOutputStream output = null;
-            try {
-                BufferedImage image = ImageIO.read(new FileInputStream(sheet));
-                image = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, null);
-                Graphics2D g = image.createGraphics();// 得到图形上下文
-                g.setColor(Color.red); // 设置画笔颜色
-                // 设置字体
-                g.setFont(new Font("微软雅黑", Font.LAYOUT_LEFT_TO_RIGHT, 30));
-                // 写入签名
-                if (trackList != null && !trackList.isEmpty()) {
-                    for (int i = 0; i < trackList.size(); i++) {
-                        MarkTrack t = trackList.get(i);
-                        BigDecimal left = new BigDecimal(t.getOffsetY());
-                        BigDecimal top = new BigDecimal(t.getOffsetX());
-                        g.drawString(t.getScore().toString(), left.intValue(), top.intValue());
-                    }
-                }
-                g.dispose();
-
-                output = new FileOutputStream(track);
-                ImageIO.write(image, "jpg", output);
-            } catch (Exception e) {
-                log.error(SystemConstant.LOG_ERROR, e);
-            } finally {
-                if (Objects.nonNull(output)) {
-                    output.flush();
-                    output.close();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void sheetExport(Long examId, String coursePaperId, HttpServletResponse response) {
-        List<MarkStudent> list = this.listByExamIdAndCoursePaperId(examId,coursePaperId);
-        if (CollectionUtils.isNotEmpty(list)) {
-            try {
-                response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("原图.zip","UTF-8"));
-                ZipWriter writer = ZipWriter.create(response.getOutputStream());
-                ByteArrayOutputStream os = new ByteArrayOutputStream();
-                for (MarkStudent s:list) {
-                    if(!s.getUpload()){
-                        continue;
-                    }
-                    List<FilePathVo> vos = JSON.parseArray(s.getSheetPath(), FilePathVo.class);
-                    for (int i = 0; i < vos.size(); i++) {
-                        FilePathVo vo = vos.get(i);
-                        File file =  fileUploadService.downloadFile(JSON.toJSONString(vo),vo.getPath());
-                        String format = FilenameUtils.getExtension(file.getName());
-                        writer.write(file, s.getStudentCode()+"-"+(i+1)+"."+format);
-                        file.delete();
-                    }
-                }
-                writer.close();
-            }catch (Exception e){
-                e.printStackTrace();
-                throw new ParameterException("文件下载失败", e);
-            }
-        }
-    }
-}
+package com.qmth.teachcloud.mark.service.impl;
+
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorConvertOp;
+import java.io.*;
+import java.math.BigDecimal;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotNull;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.FileCopyUtils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.Image;
+import com.itextpdf.text.PageSize;
+import com.itextpdf.text.Rectangle;
+import com.itextpdf.text.pdf.PdfWriter;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.tools.excel.ExcelWriter;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.boot.tools.io.ZipWriter;
+import com.qmth.teachcloud.common.bean.dto.DataPermissionRule;
+import com.qmth.teachcloud.common.bean.vo.FilePathVo;
+import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.entity.BasicExam;
+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.PageSizeEnum;
+import com.qmth.teachcloud.common.enums.ScanStatus;
+import com.qmth.teachcloud.common.enums.mark.MarkPaperStatus;
+import com.qmth.teachcloud.common.enums.mark.SubjectiveStatus;
+import com.qmth.teachcloud.common.service.BasicRoleDataPermissionService;
+import com.qmth.teachcloud.common.service.FileUploadService;
+import com.qmth.teachcloud.common.service.TeachcloudCommonService;
+import com.qmth.teachcloud.common.util.*;
+import com.qmth.teachcloud.mark.bean.UpdateTimeVo;
+import com.qmth.teachcloud.mark.bean.archivescore.*;
+import com.qmth.teachcloud.mark.bean.omredit.OmrEditDomain;
+import com.qmth.teachcloud.mark.bean.omredit.OmrEditPaper;
+import com.qmth.teachcloud.mark.bean.scananswer.*;
+import com.qmth.teachcloud.mark.bean.scanexaminfo.CheckTask;
+import com.qmth.teachcloud.mark.bean.scanexaminfo.ScanExamCheckInfoVo;
+import com.qmth.teachcloud.mark.bean.scanexaminfo.ScanExamInfoVo;
+import com.qmth.teachcloud.mark.bean.student.AbsentManualUpdateVo;
+import com.qmth.teachcloud.mark.bean.student.StudentQuery;
+import com.qmth.teachcloud.mark.bean.student.StudentVo;
+import com.qmth.teachcloud.mark.dto.UnexistStudentDto;
+import com.qmth.teachcloud.mark.dto.mark.ScoreInfo;
+import com.qmth.teachcloud.mark.dto.mark.ScoreItem;
+import com.qmth.teachcloud.mark.dto.mark.manage.Task;
+import com.qmth.teachcloud.mark.dto.mark.score.*;
+import com.qmth.teachcloud.mark.entity.*;
+import com.qmth.teachcloud.mark.enums.ExamStatus;
+import com.qmth.teachcloud.mark.enums.LockType;
+import com.qmth.teachcloud.mark.enums.OmrTaskStatus;
+import com.qmth.teachcloud.mark.lock.LockService;
+import com.qmth.teachcloud.mark.mapper.MarkStudentMapper;
+import com.qmth.teachcloud.mark.params.MarkHeaderResult;
+import com.qmth.teachcloud.mark.service.*;
+import com.qmth.teachcloud.mark.utils.BatchGetDataUtil;
+import com.qmth.teachcloud.mark.utils.Calculator;
+import com.qmth.teachcloud.mark.utils.ScoreCalculateUtil;
+
+/**
+ * <p>
+ * 考试考生库 服务实现类
+ * </p>
+ *
+ * @author xf
+ * @since 2023-09-22
+ */
+@Service
+public class MarkStudentServiceImpl extends ServiceImpl<MarkStudentMapper, MarkStudent> implements MarkStudentService {
+
+    @Autowired
+    private MarkPaperService markPaperService;
+
+    @Resource
+    private MarkPaperPackageService markPaperPackageService;
+
+    @Autowired
+    private ScanPackageService scanPackageService;
+
+    @Autowired
+    private ScanPaperService scanPaperService;
+
+    @Autowired
+    private ScanPaperPageService scanPaperPageService;
+
+    @Autowired
+    private ScanOmrTaskService scanOmrTaskService;
+
+    @Autowired
+    private ScanAnswerCardService answerCardService;
+
+    @Autowired
+    private ScanStudentPaperService studentPaperService;
+
+    @Resource
+    private MarkQuestionService markQuestionService;
+
+    @Resource
+    private TeachcloudCommonService teachcloudCommonService;
+
+    @Autowired
+    private ConcurrentService concurrentService;
+
+    @Resource
+    private MarkService markService;
+
+    @Resource
+    private LockService lockService;
+
+    @Resource
+    private TaskService taskService;
+
+    @Resource
+    private BasicRoleDataPermissionService basicRoleDataPermissionService;
+
+    @Autowired
+    private MarkSubjectiveScoreService markSubjectiveScoreService;
+
+    @Resource
+    private MarkSyncService markSyncService;
+
+    @Resource
+    private FileUploadService fileUploadService;
+    @Resource
+    private MarkTrackService markTrackService;
+
+    @Override
+    public List<String> listClassByExamIdAndCourseCode(Long examId, String paperNumber) {
+        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber);
+        List<MarkStudent> markStudentList = this.list(queryWrapper);
+
+        List<String> classNameList = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(markStudentList)) {
+            classNameList = markStudentList.stream().filter(m -> StringUtils.isNotBlank(m.getClassName()))
+                    .map(MarkStudent::getClassName).distinct().collect(Collectors.toList());
+        }
+        return classNameList;
+    }
+
+    @Override
+    public void updateSubjectiveStatusAndScore(Long studentId, SubjectiveStatus status, Double score,
+                                               String scoreList) {
+        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkStudent::getSubjectiveStatus, status).set(MarkStudent::getSubjectiveScore, score)
+                .set(MarkStudent::getSubjectiveScoreList, scoreList).eq(MarkStudent::getId, studentId);
+        this.update(updateWrapper);
+    }
+
+    @Override
+    public void updateSubjectiveStatusAndScore(Long examId, String paperNumber, SubjectiveStatus status, Double score,
+                                               String scoreList) {
+        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkStudent::getSubjectiveStatus, status).set(MarkStudent::getSubjectiveScore, score)
+                .set(MarkStudent::getSubjectiveScoreList, scoreList)
+                .set(MarkStudent::getCheckUserId, null)
+                .set(MarkStudent::getCheckTime, null).eq(MarkStudent::getExamId, examId)
+                .eq(MarkStudent::getPaperNumber, paperNumber);
+        this.update(updateWrapper);
+    }
+
+    @Override
+    public ScanExamInfoVo getScanExamInfo(BasicExam exam, String courseCode, String coursePaperId) {
+        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
+        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
+                sysUser.getId(), ServletUtil.getRequest().getServletPath());
+        ScanExamInfoVo vo = new ScanExamInfoVo();
+        vo.setId(exam.getId());
+        vo.setName(exam.getName());
+        vo.getAnswerScan().setCourseCount(
+                markPaperService.getCourseCount(exam.getId(), courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        vo.getAnswerScan().setPaperNumberCount(markPaperService.getPaperNumberCount(exam.getId(), courseCode,
+                coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        vo.getAnswerScan()
+                .setTotalCount(getCount(exam.getId(), null, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        vo.getAnswerScan().setScannedCount(
+                getCount(exam.getId(), ScanStatus.SCANNED, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        vo.getPackageScan().setTotalCount(markPaperPackageService.getPackageCountByExamId(exam.getId(), courseCode,
+                coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        vo.getPackageScan().setScannedCount(
+                scanPackageService.getCount(exam.getId(), courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        return vo;
+    }
+
+    @Override
+    public IPage<StudentScoreDetailDto> pageStudentScore(Long examId, String paperNumber, String college, String majorName,
+                                                         String className, String teacher, Integer filter, String status, Boolean breach, Double startScore,
+                                                         Double endScore, Double subScore, Integer objectiveScoreRateLt, String studentName, String studentCode,
+                                                         String orderType, String orderField, Integer pageNumber, Integer pageSize) {
+        if (startScore != null && endScore == null) {
+            throw ExceptionResultEnum.ERROR.exception("请输入结束分数值");
+        }
+        Page<StudentScoreDetailDto> page = new Page<>(pageNumber, pageSize);
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        Double objectiveScoreLt = objectiveScoreRateLt == null ? null : Calculator.round(Calculator.divide(Calculator.multiply(markPaper.getObjectiveScore(), Double.parseDouble(String.valueOf(objectiveScoreRateLt))), 100), 2);
+        IPage<StudentScoreDetailDto> studentScoreDetailDtoIPage = this.baseMapper.pageStudentScore(page, examId,
+                paperNumber, college, majorName, className, teacher, filter, status, breach, startScore, endScore, subScore,
+                objectiveScoreLt, studentName, studentCode, orderType, orderField);
+        for (StudentScoreDetailDto scoreDetailDto : studentScoreDetailDtoIPage.getRecords()) {
+            // 原图
+            scoreDetailDto.setSheetUrls(buildSheetUrls(scoreDetailDto.getStudentId()));
+            scoreDetailDto.setSheetPath(null);
+            // 状态
+            if (ScanStatus.UNEXIST.equals(scoreDetailDto.getScanStatus())) {
+                scoreDetailDto.setStatusDisplay("未扫描");
+            } else if (ScanStatus.MANUAL_ABSENT.equals(scoreDetailDto.getScanStatus()) || scoreDetailDto.getAbsent() || scoreDetailDto.getOmrAbsent()) {
+                scoreDetailDto.setStatusDisplay("缺考");
+            } else if (!scoreDetailDto.getAbsent() && !scoreDetailDto.getOmrAbsent() && scoreDetailDto.getUpload() && ScanStatus.SCANNED.equals(scoreDetailDto.getScanStatus())) {
+                scoreDetailDto.setStatusDisplay("正常");
+            }
+
+            // 主观题检查标记
+            scoreDetailDto.setSubjectiveCheckFlag(!scoreDetailDto.getAbsent() && !scoreDetailDto.getOmrAbsent() && scoreDetailDto.getUpload() && ScanStatus.SCANNED.equals(scoreDetailDto.getScanStatus()) && SubjectiveStatus.MARKED.equals(scoreDetailDto.getSubjectiveStatus()) && StringUtils.isNotBlank(scoreDetailDto.getSubjectiveScore()) && StringUtils.isNotBlank(scoreDetailDto.getSubjectiveScoreList()));
+            // 客观题检查标记
+            scoreDetailDto.setObjectiveCheckFlag(!scoreDetailDto.getAbsent() && !scoreDetailDto.getOmrAbsent() && scoreDetailDto.getUpload() && ScanStatus.SCANNED.equals(scoreDetailDto.getScanStatus()) && StringUtils.isNotBlank(scoreDetailDto.getObjectiveScore()) && StringUtils.isNotBlank(scoreDetailDto.getObjectiveScoreList()));
+
+            // 格式化分数
+            scoreDetailDto.setObjectiveScore(Calculator.round(scoreDetailDto.getObjectiveScore(), 1));
+            scoreDetailDto.setSubjectiveScore(Calculator.round(scoreDetailDto.getSubjectiveScore(), 1));
+            scoreDetailDto.setTotalScore(Calculator.round(scoreDetailDto.getTotalScore(), 1));
+        }
+        return studentScoreDetailDtoIPage;
+    }
+
+    @Override
+    public List<SheetUrlDto> buildSheetUrls(Long studentId) {
+        // 原图
+        List<SheetUrlDto> sheetUrls = new ArrayList<>();
+        List<StudentPaperDetailDto> studentPaperDetailDtoList = scanPaperService.listStudentPaperDetail(studentId);
+        for (int i = 0; i < studentPaperDetailDtoList.size(); i++) {
+            StudentPaperDetailDto studentPaperDetailDto = studentPaperDetailDtoList.get(i);
+            sheetUrls.add(new SheetUrlDto(
+                    2 * (studentPaperDetailDto.getPaperIndex() - 1) + studentPaperDetailDto.getPageIndex(),
+                    teachcloudCommonService.filePreview(studentPaperDetailDto.getSheetPath()),
+                    studentPaperDetailDto.getRecogData()));
+        }
+        return sheetUrls;
+    }
+
+    private int getCount(Long examId, ScanStatus status, String courseCode, String coursePaperId,
+                         MarkPaperStatus markPaperStatus, DataPermissionRule dpr) {
+        MarkStudent markStudent = new MarkStudent();
+        markStudent.setExamId(examId);
+        markStudent.setCourseCode(courseCode);
+        markStudent.setCoursePaperId(coursePaperId);
+        markStudent.setScanStatus(status);
+        markStudent.setMarkPaperStatus(markPaperStatus.name());
+        return baseMapper.selectCountByQuery(markStudent, dpr);
+    }
+
+    private int getOmrAbsentCount(Long examId, Boolean checked, String courseCode, String coursePaperId,
+                                  MarkPaperStatus status, DataPermissionRule dpr) {
+        MarkStudent markStudent = new MarkStudent();
+        markStudent.setExamId(examId);
+        markStudent.setCourseCode(courseCode);
+        markStudent.setCoursePaperId(coursePaperId);
+        markStudent.setOmrAbsent(true);
+        markStudent.setOmrAbsentChecked(checked);
+        markStudent.setMarkPaperStatus(status.name());
+        return baseMapper.selectCountByQuery(markStudent, dpr);
+    }
+
+    private int getIncompleteCount(Long examId, String courseCode, String coursePaperId, MarkPaperStatus status,
+                                   DataPermissionRule dpr) {
+        MarkStudent markStudent = new MarkStudent();
+        markStudent.setExamId(examId);
+        markStudent.setCourseCode(courseCode);
+        markStudent.setCoursePaperId(coursePaperId);
+        markStudent.setIncomplete(true);
+        markStudent.setMarkPaperStatus(status.name());
+        return baseMapper.selectCountByQuery(markStudent, dpr);
+    }
+
+    @Override
+    public ScanExamCheckInfoVo checkInfo(BasicExam exam, String courseCode, String coursePaperId) {
+        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
+        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
+                sysUser.getId(), ServletUtil.getRequest().getServletPath());
+        Long examId = exam.getId();
+        ScanExamCheckInfoVo vo = new ScanExamCheckInfoVo();
+        vo.setId(exam.getId());
+        vo.setName(exam.getName());
+        CheckTask ct = vo.getCheckTask();
+        ct.setUnexistCount(
+                getCount(examId, ScanStatus.UNEXIST, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        ct.setUnexistCheckedCount(
+                getCount(examId, ScanStatus.MANUAL_ABSENT, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        ct.setAssignedCount(getAssignedCount(examId, false, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        ct.setAssignedCheckedCount(
+                getAssignedCount(examId, true, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        ct.setAbsentCheckCount(
+                getOmrAbsentCount(examId, false, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        ct.setAbsentCheckedCount(
+                getOmrAbsentCount(examId, true, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        ct.setObjectiveCheckCount(scanOmrTaskService.getFinishStudentCountByExamAndUserId(examId, courseCode, coursePaperId,
+                OmrTaskStatus.WAITING.name(), dpr));
+        ct.setObjectiveCheckedCount(scanOmrTaskService.getFinishStudentCountByExamAndUserId(examId, courseCode, coursePaperId,
+                OmrTaskStatus.PROCESSED.name(), dpr));
+        ct.setIncompleteCount(getIncompleteCount(examId, courseCode, coursePaperId, MarkPaperStatus.FORMAL, dpr));
+        return vo;
+    }
+
+    /**
+     * 根据考生当前绑定的paper刷新考生状态,需要在外部调用处对考生上锁
+     */
+    @Override
+    @Transactional
+    public void updateStudentByPaper(@NotNull Long userId, @NotNull Long studentId, @NotNull boolean updateOmrTask) {
+        MarkStudent student = this.getById(studentId);
+        if (student == null) {
+            throw new ParameterException("找不到对应的考生");
+        }
+        // 重置状态
+        student.setIncomplete(false);
+        student.setAssigned(false);
+        student.setInvalid(false);
+        student.setQuestionFilled(false);
+        // student.setOmrAbsent(false);
+        int paperCount = 0;
+        List<ScanStudentPaper> studentPaperList = studentPaperService.findByStudentId(studentId);
+        List<String> objectiveAnswers = new ArrayList<>();
+        for (ScanStudentPaper studentPaper : studentPaperList) {
+            paperCount++;
+            // 获取paper详情更新考生状态
+            ScanPaper paper = scanPaperService.getById(studentPaper.getPaperId());
+            student.setAssigned(student.getAssigned() || paper.getAssigned());
+            student.setInvalid(student.getInvalid() || paper.getInvalid());
+            student.setQuestionFilled(student.getQuestionFilled() || paper.getQuestionFilled());
+            student.setCardNumber(paper.getCardNumber());
+            // 单独判断首张纸正面的识别结果
+            if (studentPaper.getPaperIndex() == 1) {
+                // 根据识别结果更新考生属性
+                ScanPaperPage page = scanPaperPageService.findPaperIdAndIndex(paper.getId(), 1);
+                student.setOmrAbsent(page.getAbsent() == null ? false : page.getAbsent().getResult());
+                student.setOmrAbsentChecked(false);
+                if (student.getOmrAbsent()) {
+                    student.setObjectiveScore(null);
+                    student.setObjectiveScoreList(null);
+                }
+                // student.setDevice(batchService.findByPaperId(paper.getId()).getDevice());
+            }
+
+            List<ScanPaperPage> scanPaperPages = scanPaperPageService.listByPaperId(studentPaper.getPaperId());
+            for (ScanPaperPage scanPaperPage : scanPaperPages) {
+                if (scanPaperPage.getQuestion() != null
+                        && CollectionUtils.isNotEmpty(scanPaperPage.getQuestion().getResult())) {
+                    for (String s : scanPaperPage.getQuestion().getResult()) {
+                        if (s.startsWith("?")) {
+                            s = s.replace("?", "");
+                        }
+                        objectiveAnswers.add(s);
+                    }
+                }
+            }
+        }
+        student.setAnswers(JSON.toJSONString(objectiveAnswers));
+        // 更新考生状态
+        if (paperCount > 0) {
+            ScanAnswerCard answerCard = answerCardService.findByExamAndNumber(student.getExamId(),
+                    student.getCardNumber());
+            student.setUpload(true);
+            student.setIncomplete(paperCount != answerCard.getPaperCount());
+            student.setScanStatus(ScanStatus.SCANNED);
+            student.setAbsent(false);
+            student.setManualAbsent(false);
+            // 更新图片数量和图片地址
+            updateStudentSheetInfo(student, studentPaperList);
+        } else {
+            student.setScanStatus(ScanStatus.UNEXIST);
+        }
+        // student.setUpdaterId(userId);
+        // student.setUpdateTime(System.currentTimeMillis());
+        this.saveOrUpdate(student);
+        // 客观题统分
+        this.calculateObjectiveScore(student);
+
+        // 更新课程表中上传人数
+        markPaperService.updateUploadCount(student.getExamId(), student.getPaperNumber(),
+                this.countUploadedByExamIdAndPaperNumber(student.getExamId(), student.getPaperNumber()));
+        markPaperService.updateAbsentCount(student.getExamId(), student.getPaperNumber(),
+                this.countAbsentByExamIdAndPaperNumber(student.getExamId(), student.getPaperNumber()));
+        if (updateOmrTask) {
+            // 清除识别对照任务
+            scanOmrTaskService.deleteByStudentId(student.getExamId(), student.getId());
+            // 重新生成识别对照任务
+            scanOmrTaskService.saveTask(student.getId());
+        }
+    }
+
+    private MarkStudent updateStudentSheetInfo(MarkStudent student, List<ScanStudentPaper> studentPaperList) {
+        List<FilePathVo> filePathVoList = new ArrayList<>();
+        for (ScanStudentPaper scanStudentPaper : studentPaperList) {
+            List<ScanPaperPage> scanPaperPages = scanPaperPageService.listByPaperId(scanStudentPaper.getPaperId());
+            for (ScanPaperPage scanPaperPage : scanPaperPages) {
+                String sheetPath = scanPaperPage.getSheetPath();
+                if (StringUtils.isNotBlank(sheetPath)) {
+                    filePathVoList.add(JSON.parseObject(sheetPath, FilePathVo.class));
+                }
+            }
+        }
+        student.setSheetCount(filePathVoList.size());
+        student.setSheetPath(JSON.toJSONString(filePathVoList));
+        return student;
+    }
+
+    @Override
+    public MarkStudent findByExamIdAndCoursePaperIdAndStudentCode(Long examId, String coursePaperId,
+                                                                  String studentCode) {
+        QueryWrapper<MarkStudent> wrapper = new QueryWrapper<>();
+        LambdaQueryWrapper<MarkStudent> lw = wrapper.lambda();
+        lw.eq(MarkStudent::getExamId, examId);
+        lw.eq(MarkStudent::getCoursePaperId, coursePaperId);
+        lw.eq(MarkStudent::getStudentCode, studentCode);
+        return baseMapper.selectOne(wrapper);
+    }
+
+    @Override
+    public StudentObjectiveDetailDto getObjectiveInspectedTask(Long studentId) {
+        MarkStudent markStudent = this.getById(studentId);
+        StudentObjectiveDetailDto studentObjectiveDetailDto = new StudentObjectiveDetailDto();
+        if (markStudent != null) {
+            studentObjectiveDetailDto.setStudentId(markStudent.getId());
+            studentObjectiveDetailDto.setStudentName(markStudent.getStudentName());
+            studentObjectiveDetailDto.setStudentCode(markStudent.getStudentCode());
+            studentObjectiveDetailDto.setExamPlace(markStudent.getExamPlace());
+            studentObjectiveDetailDto.setExamRoom(markStudent.getExamRoom());
+            studentObjectiveDetailDto.setExamId(markStudent.getExamId());
+            studentObjectiveDetailDto.setCourseCode(markStudent.getCourseCode());
+            studentObjectiveDetailDto.setCourseName(markStudent.getCourseName());
+            studentObjectiveDetailDto.setPaperNumber(markStudent.getPaperNumber());
+            studentObjectiveDetailDto
+                    .setObjectiveScore(markStudent.getObjectiveScore() != null ? markStudent.getObjectiveScore() : 0);
+            studentObjectiveDetailDto.setSubjectiveScore(
+                    markStudent.getSubjectiveScore() != null ? markStudent.getSubjectiveScore() : 0);
+            studentObjectiveDetailDto.setUpload(markStudent.getUpload());
+            studentObjectiveDetailDto.setAbsent(markStudent.getAbsent());
+            studentObjectiveDetailDto.setSheetUrls(this.buildSheetUrls(studentId));
+
+            List<MarkQuestion> questions = markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(
+                    markStudent.getExamId(), markStudent.getPaperNumber(), null, true);
+            List<String> answers = markStudent.getAnswerList();
+            int questionCount = questions.size();
+            int answerCount = answers.size();
+
+            List<StudentObjectiveAnswerDto> answerDtoList = new ArrayList<>();
+            Map<Integer, String> titles = new HashMap<>();
+            // 已设置客观题
+            int maxCount = Math.max(questionCount, answerCount);
+            for (int i = 0; i < maxCount; i++) {
+                MarkQuestion q = questionCount > i ? questions.get(i) : null;
+                String answer = answerCount > i ? answers.get(i) : "#";
+                StudentObjectiveAnswerDto studentObjectiveAnswerDto = new StudentObjectiveAnswerDto();
+                studentObjectiveAnswerDto.setMainNumber(q != null ? q.getMainNumber() : 0);
+                studentObjectiveAnswerDto.setSubNumber(q != null ? q.getSubNumber() : 0);
+                studentObjectiveAnswerDto.setAnswer(answer);
+                studentObjectiveAnswerDto.setExist(q != null && q.getTotalScore() > 0);
+                studentObjectiveAnswerDto.setQuestionType(q.getQuestionType());
+                answerDtoList.add(studentObjectiveAnswerDto);
+
+                if (q != null) {
+                    titles.put(q.getMainNumber(), q.getMainTitle());
+                }
+            }
+            studentObjectiveDetailDto.setAnswers(answerDtoList);
+            studentObjectiveDetailDto.setTitles(titles);
+        }
+
+        return studentObjectiveDetailDto;
+    }
+
+    @Override
+    public Boolean saveObjectiveInspectedTask(Long studentId, String answers) {
+        Long userId = ServletUtil.getRequestUserId();
+        MarkStudent student = this.getById(studentId);
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(student.getExamId(), student.getPaperNumber());
+        // 评卷是否结束
+        if (markPaper == null || MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
+            throw ExceptionResultEnum.ERROR.exception("科目已结束评卷,无法打分");
+        }
+        answers = StringUtils.trimToEmpty(answers);
+        if (student != null) {
+            student.setAnswers(answers.toUpperCase());
+            student.setCheckUserId(userId);
+            student.setCheckTime(System.currentTimeMillis());
+            return saveUploadStudent(student);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int countUploadedByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
+                .eq(MarkStudent::getUpload, true)
+                .eq(MarkStudent::getScanStatus, ScanStatus.SCANNED)
+                .eq(MarkStudent::getAbsent, false)
+                .eq(MarkStudent::getBreach, false)
+                .eq(MarkStudent::getOmrAbsent, false);
+        return this.count(queryWrapper);
+    }
+
+    @Override
+    public boolean updateScanInfo(MarkStudent student) {
+        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkStudent::getAnswers, student.getAnswers())
+                .set(MarkStudent::getBatchCode, student.getBatchCode()).set(MarkStudent::getAbsent, student.getAbsent())
+                .set(MarkStudent::getUploadTime, System.currentTimeMillis())
+                .set(MarkStudent::getCardNumber, student.getCardNumber()).eq(MarkStudent::getId, student.getId());
+        return this.update(updateWrapper);
+
+    }
+
+    @Override
+    public List<MarkStudent> listAbsentOrBreachMarkTaskStudent(Long examId, String paperNumber) {
+        return this.baseMapper.listAbsentOrBreachMarkTaskStudent(examId, paperNumber);
+    }
+
+    @Override
+    public List<MarkStudent> listUnMarkTaskStudent(Long examId, String paperNumber, Integer groupNumber, int pageSize) {
+        Page<MarkStudent> page = new Page<>(1, pageSize);
+        IPage<MarkStudent> markStudentIPage = this.baseMapper.listUnMarkTaskStudent(page, examId, paperNumber,
+                groupNumber);
+        return markStudentIPage.getRecords();
+    }
+
+    /**
+     * 客观题统分 统分场景: 1.后台-成绩检查-客观题检查 2.扫描端-考生图片上传 3.扫描端-客观题二次识别
+     *
+     * @param student
+     * @return
+     */
+    @Override
+    public boolean saveUploadStudent(MarkStudent student) {
+        MarkStudent old = this.getById(student.getId());
+        if (!student.getAbsent()) {// 正考
+            MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(student.getExamId(),
+                    student.getPaperNumber());
+            if (markPaper.getStatus().equals(MarkPaperStatus.FINISH)) {
+                markPaperService.updateStatus(markPaper.getExamId(), markPaper.getPaperNumber(), MarkPaperStatus.FORMAL,
+                        MarkPaperStatus.FINISH);
+            }
+        }
+        calculateObjectiveScore(student);
+        if (student.getAbsent()) {// 转缺考
+            student.setObjectiveScore(null);
+            student.setObjectiveScoreList(null);
+        }
+        if (!old.getAbsent() && student.getAbsent()) {// 正考转缺考
+            student.setSubjectiveScore(null);
+            student.setSubjectiveScoreList(null);
+            student.setSubjectiveStatus(SubjectiveStatus.UNMARK);
+            this.updateById(student);
+        }
+        boolean success = this.updateScanInfo(student);
+        if (success) {
+            markPaperService.updateUploadCount(student.getExamId(), student.getPaperNumber(),
+                    this.countUploadedByExamIdAndPaperNumber(student.getExamId(), student.getPaperNumber()));
+        }
+        return success;
+    }
+
+    @Override
+    public void calculateObjectiveScore(MarkStudent student) {
+        // 缺考状态不统分(人工指定缺考、识别缺考)
+        if (!ScanStatus.MANUAL_ABSENT.equals(student.getScanStatus()) && !student.getAbsent() && !student.getOmrAbsent()) {
+            ScoreCalculateUtil util = ScoreCalculateUtil.instance(student);
+            ScoreInfo info = util.calculate(markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(
+                    student.getExamId(), student.getPaperNumber(), null, true), null);
+            student.setObjectiveScore(info.getObjectiveScore());
+            student.setScoreList(info.getScoreList(), true);
+            this.updateObjectiveScoreAndScoreList(student);
+        }
+    }
+
+    @Override
+    @Transactional
+    public void updateStudentAndPaper(@NotNull SysUser user, @NotNull Long id,
+                                      @NotNull List<ScanStudentPaper> studentPaperList) {
+        if (CollectionUtils.isNotEmpty(studentPaperList)) {
+            for (ScanStudentPaper studentPaper : studentPaperList) {
+                studentPaper.setStudentId(id);
+            }
+        }
+        // 清空原有绑定关系
+        if (studentPaperService.removeByStudentId(id) > 0) {
+            // 删除评卷任务
+            MarkStudent student = this.getById(id);
+            markService.deleteMarkTaskByStudent(student);
+        }
+        if (CollectionUtils.isNotEmpty(studentPaperList)) {
+            // 保存绑定关系
+            studentPaperService.saveOrUpdateBatch(studentPaperList);
+        }
+        // 更新考生状态
+        updateStudentByPaper(user.getId(), id, true);
+    }
+
+    @Override
+    public StudentVo findOne(StudentQuery query) {
+        return baseMapper.findOne(query);
+    }
+
+    @Override
+    public int countByExamIdAndSecretNumber(Long examId, String secretNumber) {
+        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getSecretNumber, secretNumber);
+        return this.count(queryWrapper);
+    }
+
+    @Override
+    public List<MarkStudent> listByExamIdAndCoursePaperId(Long examId, String coursePaperId) {
+        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getCoursePaperId, coursePaperId);
+        return this.list(queryWrapper);
+    }
+
+    @Override
+    public IPage<AnswerQueryVo> query(AnswerQueryDomain query) {
+        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
+        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
+                sysUser.getId(), ServletUtil.getRequest().getServletPath());
+        // 查询考生分页信息
+        query.setMarkPaperStatus(MarkPaperStatus.FORMAL.name());
+        IPage<AnswerQueryVo> iPage = baseMapper.queryPage(new Page<>(query.getPageNumber(), query.getPageSize()), query,
+                dpr);
+        if (CollectionUtils.isNotEmpty(iPage.getRecords())) {
+            for (AnswerQueryVo vo : iPage.getRecords()) {
+                if (vo.getIsAbsent() != null && vo.getIsAbsent()) {
+                    vo.setExamStatus(ExamStatus.ABSENT);
+                } else {
+                    vo.setExamStatus(ExamStatus.OK);
+                }
+            }
+        }
+        if (CollectionUtils.isNotEmpty(iPage.getRecords()) && (query.getWithPaper() != null && query.getWithPaper())) {
+            Map<Long, AnswerQueryVo> map = new HashMap<>();
+
+            for (AnswerQueryVo vo : iPage.getRecords()) {
+                List<AnswerPaperVo> papers = new ArrayList<>();
+                vo.setPapers(papers);
+                if (vo.getCardPaperCount() != null) {
+                    for (int i = 1; i <= vo.getCardPaperCount(); i++) {
+                        AnswerPaperVo pv = new AnswerPaperVo();
+                        pv.setNumber(i);
+                        papers.add(pv);
+                    }
+                }
+                map.put(vo.getId(), vo);
+            }
+            // 根据考生id查找绑定paper
+            List<Long> studentIds = iPage.getRecords().stream().map(p -> p.getId()).collect(Collectors.toList());
+            List<StudentPaperVo> paperList = new BatchGetDataUtil<StudentPaperVo, Long>() {
+
+                @Override
+                public List<StudentPaperVo> getData(List<Long> paramList) {
+                    return scanPaperService.listByStudentIds(paramList);
+                }
+            }.getDataForBatch(studentIds, 200);
+
+            if (CollectionUtils.isNotEmpty(paperList)) {
+                Map<Long, AnswerPaperVo> paperMap = new HashMap<>();
+                for (StudentPaperVo p : paperList) {
+                    AnswerQueryVo vo = map.get(p.getStudentId());
+                    if (vo == null) {
+                        continue;
+                    }
+                    List<AnswerPaperVo> papers = vo.getPapers();
+                    if (papers == null) {
+                        continue;
+                    }
+                    if (papers.size() < p.getNumber()) {
+                        continue;
+                    }
+                    AnswerPaperVo pvo = papers.get(p.getNumber() - 1);
+                    pvo.setId(p.getPaperId());
+                    pvo.setNumber(p.getNumber());
+                    pvo.setAssigned(p.getAssigned());
+                    pvo.setInvalid(p.getInvalid());
+                    paperMap.put(p.getPaperId(), pvo);
+                }
+                // 查找page
+                List<Long> paperIds = paperList.stream().map(p -> p.getPaperId()).collect(Collectors.toList());
+                List<ScanPaperPage> paperPageList = new BatchGetDataUtil<ScanPaperPage, Long>() {
+
+                    @Override
+                    public List<ScanPaperPage> getData(List<Long> paramList) {
+                        return scanPaperPageService.listByPaperList(paramList);
+                    }
+                }.getDataForBatch(paperIds, 200);
+
+                if (CollectionUtils.isNotEmpty(paperPageList)) {
+                    for (ScanPaperPage p : paperPageList) {
+                        AnswerPaperVo pvo = paperMap.get(p.getPaperId());
+                        if (pvo == null) {
+                            continue;
+                        }
+                        List<AnswerPageVo> pages = pvo.getPages();
+                        if (pages == null) {
+                            pages = new ArrayList<>();
+                            pvo.setPages(pages);
+                        }
+                        AnswerPageVo pageVo = new AnswerPageVo();
+                        pageVo.setIndex(p.getPageIndex());
+                        pageVo.setSheetUri(teachcloudCommonService.filePreview(p.getSheetPath()));
+                        if (query.getWithOmrDetail() != null && query.getWithOmrDetail()) {
+                            pageVo.setAbsent(p.getAbsent() != null ? p.getAbsent().getResult() : null);
+                            pageVo.setBreach(p.getBreach() != null ? p.getBreach().getResult() : null);
+                            pageVo.setQuestion(p.getQuestion() != null ? p.getQuestion().getResult() : null);
+                            pageVo.setRecogData(p.getRecogData());
+                        }
+                        pages.add(pageVo);
+                    }
+                }
+            }
+        }
+        return iPage;
+    }
+
+    @Override
+    public List<String> summary(AnswerQueryDomain query) {
+        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
+        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
+                sysUser.getId(), ServletUtil.getRequest().getServletPath());
+
+        // 不分页查询考生准考证号
+        query.setMarkPaperStatus(MarkPaperStatus.FORMAL.name());
+        return baseMapper.querySummary(query, dpr);
+    }
+
+    @Transactional
+    @Override
+    public UpdateTimeVo omrEdit(Long userId, OmrEditDomain domain) {
+        MarkStudent student = findByExamIdAndCoursePaperIdAndStudentCode(domain.getExamId(), domain.getCoursePaperId(),
+                domain.getStudentCode());
+        if (student == null) {
+            throw new ParameterException("考生信息未找到");
+        }
+        concurrentService.getReadWriteLock(LockType.STUDENT + "-" + student.getId()).writeLock().lock();
+        try {
+            for (OmrEditPaper paperEdit : domain.getPapers()) {
+                ScanStudentPaper sp = studentPaperService.findByStudentIdAndPaperNumber(student.getId(),
+                        paperEdit.getNumber());
+                if (sp == null) {
+                    throw new ParameterException("未找到绑定扫描结果");
+                }
+                ScanPaper paperEntity = scanPaperService.getById(sp.getPaperId());
+                if (paperEntity == null) {
+                    throw new ParameterException("未找到paper信息结果");
+                }
+                paperEntity.setUpdaterId(userId);
+                paperEntity.setUpdateTime(System.currentTimeMillis());
+                List<ScanPaperPage> pages = scanPaperPageService.listByPaperId(paperEntity.getId());
+                for (ScanPaperPage pageEntity : pages) {
+                    paperEdit.updatePage(pageEntity);
+                }
+                scanPaperService.savePaperAndPages(paperEntity, pages);
+            }
+            updateStudentByPaper(userId, student.getId(), true);
+            return UpdateTimeVo.create();
+        } finally {
+            concurrentService.getReadWriteLock(LockType.STUDENT + "-" + student.getId()).writeLock().unlock();
+        }
+    }
+
+    @Override
+    public long countByExamIdAndPaperNumber(Long examId, String paperNumber, String paperType) {
+        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
+                .eq(MarkStudent::getPaperType, paperType);
+        return this.count(queryWrapper);
+    }
+
+    @Transactional
+    @Override
+    public AbsentManualUpdateVo absentManualUpdate(Long examId, String coursePaperId, String studentCode,
+                                                   ScanStatus status) {
+        MarkStudent student = findByExamIdAndCoursePaperIdAndStudentCode(examId, coursePaperId, studentCode);
+        if (student == null) {
+            throw new ParameterException("考生未找到");
+        }
+        if (ScanStatus.MANUAL_ABSENT.equals(status) && !ScanStatus.UNEXIST.equals(student.getScanStatus())) {
+            throw new ParameterException("考生不是未扫描状态");
+        }
+        LambdaUpdateWrapper<MarkStudent> lw = new LambdaUpdateWrapper<>();
+        lw.set(MarkStudent::getScanStatus, status);
+        lw.set(MarkStudent::getManualAbsent, ScanStatus.MANUAL_ABSENT.equals(status));
+        lw.set(MarkStudent::getAbsent, ScanStatus.MANUAL_ABSENT.equals(status));
+        lw.eq(MarkStudent::getId, student.getId());
+        update(lw);
+        markPaperService.updateAbsentCount(examId, student.getPaperNumber(),
+                this.countAbsentByExamIdAndPaperNumber(examId, student.getPaperNumber()));
+        return AbsentManualUpdateVo.create(status);
+    }
+
+    @Transactional
+    @Override
+    public UpdateTimeVo confirm(Long examId, String coursePaperId, String studentCode, Boolean omrAbsent) {
+        MarkStudent student = findByExamIdAndCoursePaperIdAndStudentCode(examId, coursePaperId, studentCode);
+        LambdaUpdateWrapper<MarkStudent> lw = new LambdaUpdateWrapper<>();
+        lw.set(MarkStudent::getOmrAbsentChecked, omrAbsent);
+        if (omrAbsent) {
+            lw.set(MarkStudent::getObjectiveScore, null);
+            lw.set(MarkStudent::getObjectiveScoreList, null);
+        } else {
+            lw.set(MarkStudent::getOmrAbsent, false);
+        }
+        lw.eq(MarkStudent::getId, student.getId());
+        update(lw);
+        scanOmrTaskService.deleteByStudentId(student.getExamId(), student.getId());
+        if (!omrAbsent) {
+            calculateObjectiveScore(student);
+            scanOmrTaskService.saveTask(student.getId());
+        }
+        markPaperService.updateAbsentCount(examId, student.getPaperNumber(),
+                this.countAbsentByExamIdAndPaperNumber(examId, student.getPaperNumber()));
+        return UpdateTimeVo.create();
+    }
+
+    @Override
+    public List<Long> findIdByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        return this.baseMapper.findIdByExamIdAndPaperNumber(examId, paperNumber);
+    }
+
+    @Override
+    public Task getSubjectiveInspectedTask(Long studentId) {
+        Task task = null;
+        if (studentId != null) {
+            Long userId = ServletUtil.getRequestUserId();
+            MarkStudent markStudent = this.getById(studentId);
+            markService.releaseByStudent(markStudent);
+            if (markService.applyStudent(markStudent, userId)) {
+                task = taskService.build(studentId);
+            }
+        }
+        return task;
+    }
+
+    @Transactional
+    @Override
+    public void saveSubjectiveInspectedTask(MarkHeaderResult markResult) {
+        MarkStudent markStudent = this.getById(markResult.getStudentId());
+        if (markStudent == null) {
+            throw ExceptionResultEnum.ERROR.exception("考生不存在");
+        }
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markStudent.getExamId(),
+                markStudent.getPaperNumber());
+        // 评卷是否结束
+        if (markPaper == null || MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
+            throw ExceptionResultEnum.ERROR.exception("科目已结束评卷,无法打分");
+        }
+        try {
+            lockService.watch(LockType.EXAM_SUBJECT, markStudent.getExamId(), markStudent.getPaperNumber());
+            lockService.waitlock(LockType.STUDENT, markResult.getStudentId());
+            markService.submitHeaderTask(markResult.getGroups(), markStudent);
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
+        } finally {
+            lockService.unlock(LockType.STUDENT, markResult.getStudentId());
+            lockService.unwatch(LockType.EXAM_SUBJECT, markStudent.getExamId(), markStudent.getPaperNumber());
+            markService.releaseByStudent(markStudent);
+        }
+    }
+
+    @Override
+    public IPage<ArchiveStudentVo> studentList(ArchiveStudentQuery query) {
+        Page<ArchiveStudentVo> page = new Page<>(query.getPageNumber(), query.getPageSize());
+        IPage<ArchiveStudentVo> ret = baseMapper.studentList(page, query);
+        for (ArchiveStudentVo record : ret.getRecords()) {
+            List<String> list = new ArrayList<>();
+            List<FilePathVo> vos = JSON.parseArray(StringUtils.trimToNull(record.getSheetPath()), FilePathVo.class);
+            if (CollectionUtils.isEmpty(vos)) {
+                continue;
+            }
+            for (FilePathVo filePathVo : vos) {
+                list.add(JSON.toJSONString(filePathVo));
+            }
+            record.setSheetUrls(teachcloudCommonService.filePreview(list));
+        }
+        return ret;
+    }
+
+    @Override
+    public void scoreExport(ArchiveStudentQuery query, HttpServletResponse response) {
+        //生成表头
+        String[] columnName = new String[]{"学生姓名", "学号", "学院", "班级", "课程代码", "课程名称", "客观分", "主观分",
+                "成绩"};
+        List<MarkQuestion> oQuestionList = markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(query.getExamId(), query.getPaperNumber(), null, true);
+        List<MarkQuestion> sQuestionList = markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(query.getExamId(), query.getPaperNumber(), null, false);
+        List<String> columnNameList = new ArrayList<>(Arrays.asList(columnName));
+        for (MarkQuestion question : oQuestionList) {
+            columnNameList.add(question.getMainTitle() + " " + question.getMainNumber() + "-" + question.getSubNumber() + "选项");
+            columnNameList.add(question.getMainTitle() + " " + question.getMainNumber() + "-" + question.getSubNumber() + "得分");
+        }
+        for (MarkQuestion question : oQuestionList) {
+            columnNameList.add(question.getMainTitle() + " " + question.getMainNumber() + "-" + question.getSubNumber());
+        }
+        String[] columnNames = columnNameList.toArray(new String[0]);
+        //生成动态内容
+        List<String[]> columnValues = new ArrayList<>();
+        List<ArchiveStudentVo> ret = baseMapper.studentList(query);
+        for (ArchiveStudentVo s : ret) {
+            List<String> valueList = new ArrayList<>();
+            valueList.add(s.getStudentName());
+            valueList.add(s.getStudentCode());
+            valueList.add(s.getCollege());
+            valueList.add(s.getClassName());
+            valueList.add(s.getCourseCode());
+            valueList.add(s.getCourseName());
+            valueList.add(s.getObjectiveScore() == null ? "" : s.getObjectiveScore().toString());
+            valueList.add(s.getSubjectiveScore() == null ? "" : s.getSubjectiveScore().toString());
+            valueList.add(s.getTotalScore() == null ? "" : s.getTotalScore().toString());
+            for (ScoreItem item : s.getScoreList(true, oQuestionList)) {
+                valueList.add(item.getAnswer());
+                valueList.add(item.getScore() == null ? "" : item.getScore().toString());
+            }
+            for (ScoreItem item : s.getScoreList(false, sQuestionList)) {
+                valueList.add(item.getScore().toString());
+            }
+            String[] columnValue = valueList.toArray(new String[valueList.size()]);
+            columnValues.add(columnValue);
+        }
+        try {
+            log.debug("导出Excel开始...");
+            response.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode("成绩导出", SystemConstant.CHARSET_NAME) + ".xlsx");
+            response.setContentType("application/vnd.ms-excel");
+            ServletOutputStream outputStream = response.getOutputStream();
+            ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
+            writer.writeDataArrays("成绩导出", null, columnNames, columnValues.listIterator());
+            writer.output(outputStream);
+            outputStream.flush();
+            outputStream.close();
+            log.debug("导出Excel结束");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public ScoreReportVo scoreReport(ArchiveStudentQuery query) {
+        ScoreReportVo ret = new ScoreReportVo();
+        ret.setOverview(baseMapper.overview(query));
+        if (ret.getOverview() != null) {
+            double total = ret.getOverview().getStudentCount() - ret.getOverview().getAbsentCount();
+            ret.getOverview().setPassRate(
+                    Calculator.divide2String(Calculator.multiply(ret.getOverview().getPassCount(), 100), total, 2));
+            ret.getOverview().setExcellentRate(Calculator
+                    .divide2String(Calculator.multiply(ret.getOverview().getExcellentCount(), 100), total, 2));
+            ret.getOverview().setAvgScore(Calculator.round(ret.getOverview().getAvgScore(), 2));
+        }
+        List<ArchiveStudentVo> studentList = baseMapper.studentList(query);
+        fillScoreRange(ret, studentList);
+
+        ret.setCollege(baseMapper.college(query));
+        if (CollectionUtils.isNotEmpty(ret.getCollege())) {
+            for (CollegeVo vo : ret.getCollege()) {
+                double total = vo.getStudentCount() - vo.getAbsentCount();
+                vo.setPassRate(Calculator.divide2String(Calculator.multiply(vo.getPassCount(), 100), total, 2));
+                vo.setExcellentRate(
+                        Calculator.divide2String(Calculator.multiply(vo.getExcellentCount(), 100), total, 2));
+                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
+                vo.setMinScore(Calculator.round(vo.getMinScore(), 2));
+                vo.setMaxScore(Calculator.round(vo.getMaxScore(), 2));
+            }
+        }
+
+        ret.setClassData(baseMapper.classData(query));
+        if (CollectionUtils.isNotEmpty(ret.getClassData())) {
+            for (ClassVo vo : ret.getClassData()) {
+                double total = vo.getStudentCount() - vo.getAbsentCount();
+                vo.setPassRate(Calculator.divide2String(Calculator.multiply(vo.getPassCount(), 100), total, 2));
+                vo.setExcellentRate(
+                        Calculator.divide2String(Calculator.multiply(vo.getExcellentCount(), 100), total, 2));
+                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
+                vo.setMinScore(Calculator.round(vo.getMinScore(), 2));
+                vo.setMaxScore(Calculator.round(vo.getMaxScore(), 2));
+            }
+        }
+
+        ret.setTeacher(baseMapper.teacher(query));
+        if (CollectionUtils.isNotEmpty(ret.getTeacher())) {
+            for (TeacherVo vo : ret.getTeacher()) {
+                double total = vo.getStudentCount() - vo.getAbsentCount();
+                vo.setPassRate(Calculator.divide2String(Calculator.multiply(vo.getPassCount(), 100), total, 2));
+                vo.setExcellentRate(
+                        Calculator.divide2String(Calculator.multiply(vo.getExcellentCount(), 100), total, 2));
+                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
+                vo.setMinScore(Calculator.round(vo.getMinScore(), 2));
+                vo.setMaxScore(Calculator.round(vo.getMaxScore(), 2));
+            }
+        }
+
+        ret.setTeacherClass(baseMapper.teacherClass(query));
+        if (CollectionUtils.isNotEmpty(ret.getTeacherClass())) {
+            for (TeacherClassVo vo : ret.getTeacherClass()) {
+                double total = vo.getStudentCount() - vo.getAbsentCount();
+                vo.setPassRate(Calculator.divide2String(Calculator.multiply(vo.getPassCount(), 100), total, 2));
+                vo.setExcellentRate(
+                        Calculator.divide2String(Calculator.multiply(vo.getExcellentCount(), 100), total, 2));
+                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
+            }
+        }
+
+        fillObjective(ret, studentList, query.getExamId(), query.getPaperNumber());
+        List<Long> studentIds = studentList.stream().map(ArchiveStudentVo::getStudentId).collect(Collectors.toList());
+        ret.setSubjective(markSubjectiveScoreService.getSubjectiveVo(studentIds));
+        if (CollectionUtils.isNotEmpty(ret.getSubjective())) {
+            for (QuestionVo vo : ret.getSubjective()) {
+                double total = vo.getStudentCount();
+                vo.setScoreRate(Calculator.divide(vo.getScoreCount(), total, 2));
+                vo.setFullScoreRate(Calculator.divide(vo.getFullScoreCount(), total, 2));
+                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
+            }
+        }
+        return ret;
+    }
+
+    @Override
+    public void exportUnexist(Long examId, String courseCode, String coursePaperId, HttpServletResponse response) {
+        try {
+            SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
+            DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(),
+                    sysUser.getId(), ServletUtil.getRequest().getServletPath());
+            List<UnexistStudentDto> unexistStudentDtoList = this.baseMapper.listUnexistStudentByExamIdAndCoursePaperId(
+                    examId, courseCode, coursePaperId, MarkPaperStatus.FORMAL.name(), dpr);
+            ExcelUtil.excelExport("评卷员工作量", UnexistStudentDto.class, unexistStudentDtoList, response);
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception("导出评卷员工作量失败");
+        }
+    }
+
+    @Override
+    public int countByExamIdAndPaperNumberAndMarkStatus(Long examId, String paperNumber, SubjectiveStatus status) {
+        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
+        LambdaQueryWrapper<MarkStudent> lambdaQueryWrapper = queryWrapper.lambda();
+        lambdaQueryWrapper.eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber);
+        if (status != null) {
+            lambdaQueryWrapper.eq(MarkStudent::getSubjectiveStatus, status);
+        }
+        lambdaQueryWrapper.eq(MarkStudent::getUpload, true).eq(MarkStudent::getAbsent, false)
+                .eq(MarkStudent::getBreach, false).eq(MarkStudent::getOmrAbsent, false);
+        return this.count(queryWrapper);
+    }
+
+    @Override
+    public void updateCheckInfo(Long studentId, Long userId) {
+        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkStudent::getCheckUserId, userId)
+                .set(MarkStudent::getCheckTime, System.currentTimeMillis()).eq(MarkStudent::getId, studentId);
+        this.update(updateWrapper);
+    }
+
+    @Override
+    public int countOmrAbsentStudent(Long examId, String paperNumber, String paperType, boolean isOmrAbsentConfirm) {
+        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
+                .eq(MarkStudent::getPaperType, paperType).eq(MarkStudent::getOmrAbsent, true)
+                .eq(MarkStudent::getOmrAbsentChecked, isOmrAbsentConfirm);
+        return this.count(queryWrapper);
+    }
+
+    @Override
+    public void scoreReportDownload(JSONObject jsonObject, HttpServletResponse response) {
+        String rootPath = null;
+        File htmlFile;
+        File pdfFile;
+        try {
+            // 本地保存目录
+            File tempFile = SystemConstant.getFileTempDirVar(
+                    System.currentTimeMillis() + File.separator + SystemConstant.getNanoId(),
+                    SystemConstant.TEMP_PREFIX);
+            rootPath = tempFile.getParent();
+
+            // 删除临时目录中创建的临时文件
+            if (Objects.nonNull(tempFile)) {
+                tempFile.delete();
+            }
+            // html文件
+            String cardHtmlPath = rootPath + File.separator + System.currentTimeMillis() + File.separator + "temp"
+                    + SystemConstant.HTML_PREFIX;
+            htmlFile = new File(cardHtmlPath);
+            if (!htmlFile.exists()) {
+                htmlFile.getParentFile().mkdirs();
+                htmlFile.createNewFile();
+            }
+            // 生成html文件
+            FileCopyUtils.copy(jsonObject.getString("htmlContent").getBytes(StandardCharsets.UTF_8), htmlFile);
+
+            // pdf文件
+            String cardPdfPath = rootPath + File.separator + System.currentTimeMillis() + File.separator + "temp"
+                    + SystemConstant.PDF_PREFIX;
+            pdfFile = new File(cardPdfPath);
+            if (!pdfFile.exists()) {
+                pdfFile.getParentFile().mkdirs();
+                pdfFile.createNewFile();
+            }
+            HtmlToPdfUtil.convert(cardHtmlPath, cardPdfPath, PageSizeEnum.A4);
+            FileUtil.outputFile(response, pdfFile, "报告" + SystemConstant.PDF_PREFIX);
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (Objects.nonNull(rootPath)) {
+                ConvertUtil.delFolder(rootPath);
+            }
+        }
+    }
+
+    @Override
+    public void deleteByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber);
+        this.remove(updateWrapper);
+    }
+
+    @Override
+    public boolean calcObjectiveScore(Long examId, String paperNumber) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        if (markPaper != null) {
+            if (lockService.trylock(LockType.SCORE_CALCULATE, markPaper.getId())) {
+                markSyncService.calcObjectiveScore(markPaper);
+                return true;
+            } else {
+                throw ExceptionResultEnum.ERROR.exception("评卷员正在重置");
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public IPage<MarkStudent> pageByExamAndPaperNumber(Long examId, String paperNumber, int pageNumber, int pageSize) {
+        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
+                .orderByAsc(MarkStudent::getId);
+        return this.page(new Page<>(pageNumber, pageSize), queryWrapper);
+    }
+
+    @Override
+    public void updateObjectiveScoreAndScoreList(MarkStudent markStudent) {
+        UpdateWrapper<MarkStudent> objectiveUpdateWrapper = new UpdateWrapper<>();
+        LambdaUpdateWrapper<MarkStudent> lambdaUpdateWrapper = objectiveUpdateWrapper.lambda();
+        lambdaUpdateWrapper.set(MarkStudent::getObjectiveScore, markStudent.getObjectiveScore())
+                .set(MarkStudent::getObjectiveScoreList, markStudent.getObjectiveScoreList());
+        if (markStudent.getCheckUserId() != null) {
+            lambdaUpdateWrapper.set(MarkStudent::getCheckUserId, markStudent.getCheckUserId());
+        }
+        if (markStudent.getCheckTime() != null) {
+            lambdaUpdateWrapper.set(MarkStudent::getCheckTime, markStudent.getCheckTime());
+        }
+        lambdaUpdateWrapper.eq(MarkStudent::getId, markStudent.getId());
+        this.update(objectiveUpdateWrapper);
+    }
+
+    @Override
+    public boolean updateAssignConfirm(Long studentId, boolean assignConfirm) {
+        UpdateWrapper<MarkStudent> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkStudent::getAssignConfirmed, assignConfirm).eq(MarkStudent::getId, studentId);
+        return this.update(updateWrapper);
+    }
+
+    private void fillObjective(ScoreReportVo ret, List<ArchiveStudentVo> studentList, Long examId, String paperNumber) {
+        List<MarkQuestion> qs = markQuestionService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, null, true);
+        Map<String, QuestionVo> map = new HashMap<>();
+        List<QuestionVo> list = new ArrayList<>();
+        for (ArchiveStudentVo s : studentList) {
+            List<ScoreItem> sis = s.getScoreList(true, qs);
+            if (CollectionUtils.isNotEmpty(sis)) {
+                for (ScoreItem si : sis) {
+                    String key = si.getMainNumber() + "-" + si.getSubNumber();
+                    QuestionVo vo = map.get(key);
+                    if (vo == null) {
+                        vo = new QuestionVo();
+                        vo.setScoreCount(0);
+                        vo.setFullScoreCount(0);
+                        vo.setStudentCount(0);
+                        vo.setScoreSum(0.0);
+                        vo.setMainNumber(si.getMainNumber());
+                        vo.setSubNumber(si.getSubNumber());
+                        vo.setTitle(si.getTitle());
+                        vo.setScore(si.getTotalScore());
+                        map.put(key, vo);
+                    }
+                    vo.setStudentCount(vo.getStudentCount() + 1);
+                    vo.setScoreSum(vo.getScoreSum() + si.getScore());
+                    if (Objects.nonNull(si.getScore()) && Objects.nonNull(si.getTotalScore()) && si.getScore().doubleValue() == si.getTotalScore().doubleValue()) {
+                        vo.setFullScoreCount(vo.getFullScoreCount() + 1);
+                    }
+                    if (si.getScore() > 0) {
+                        vo.setScoreCount(vo.getScoreCount() + 1);
+                    }
+                }
+            }
+        }
+        if (map.isEmpty()) {
+            ret.setObjective(list);
+            return;
+        }
+
+        list = new ArrayList<>(map.values());
+        for (QuestionVo questionVo : list) {
+            // 平均分
+            if (questionVo.getStudentCount() == null || questionVo.getStudentCount() == 0) {
+                questionVo.setAvgScore(0D);
+            } else {
+                questionVo.setAvgScore(Calculator.round(Calculator.divide(questionVo.getScoreSum(), questionVo.getStudentCount()), 2));
+            }
+        }
+
+        Collections.sort(list, (o1, o2) -> {
+            if (o1.getMainNumber() > o2.getMainNumber()) {
+                return 1;
+            } else if (o1.getSubNumber() < o2.getSubNumber()) {
+                return -1;
+            } else {
+                return 0;
+            }
+        });
+        for (QuestionVo vo : list) {
+            double total = vo.getStudentCount();
+            vo.setScoreRate(Calculator.divide(vo.getScoreCount(), total, 2));
+            vo.setFullScoreRate(Calculator.divide(vo.getFullScoreCount(), total, 2));
+        }
+        ret.setObjective(list);
+    }
+
+    private List<MarkStudent> listByExamIdAndPaperNumberAndNotAbsent(Long examId, String paperNumber) {
+        QueryWrapper<MarkStudent> wrapper = new QueryWrapper<>();
+        LambdaQueryWrapper<MarkStudent> lw = wrapper.lambda();
+        lw.eq(MarkStudent::getExamId, examId);
+        lw.eq(MarkStudent::getPaperNumber, paperNumber);
+        lw.eq(MarkStudent::getUpload, true);
+        lw.eq(MarkStudent::getAbsent, false);
+        lw.eq(MarkStudent::getBreach, false);
+        lw.eq(MarkStudent::getOmrAbsent, false);
+
+        return this.list(wrapper);
+    }
+
+    private void fillScoreRange(ScoreReportVo ret, List<ArchiveStudentVo> list) {
+        List<ScoreRangeVo> scoreRange = new ArrayList<>();
+        ret.setScoreRange(scoreRange);
+        scoreRange.add(getScoreRangeVo(list, 1.0, 9.5));
+        scoreRange.add(getScoreRangeVo(list, 10.0, 19.5));
+        scoreRange.add(getScoreRangeVo(list, 20.0, 29.5));
+        scoreRange.add(getScoreRangeVo(list, 30.0, 39.5));
+        scoreRange.add(getScoreRangeVo(list, 40.0, 49.5));
+        scoreRange.add(getScoreRangeVo(list, 50.0, 59.5));
+        scoreRange.add(getScoreRangeVo(list, 60.0, 69.5));
+        scoreRange.add(getScoreRangeVo(list, 70.0, 79.5));
+        scoreRange.add(getScoreRangeVo(list, 80.0, 89.5));
+        scoreRange.add(getScoreRangeVo(list, 90.0, 100.0));
+    }
+
+    private ScoreRangeVo getScoreRangeVo(List<ArchiveStudentVo> list, Double start, Double end) {
+        int count = (int) list.stream().filter(s -> s.getTotalScore() >= start && s.getTotalScore() <= end).count();
+        Double rate = null;
+        if (list.size() != 0) {
+            rate = Calculator.multiply(count, list.size(), 2);
+        }
+        ScoreRangeVo vo = new ScoreRangeVo(count, start, end, rate);
+        return vo;
+    }
+
+    private int getCountByPaperNumber(Long examId, String paperNumber) {
+        QueryWrapper<MarkStudent> wrapper = new QueryWrapper<>();
+        LambdaQueryWrapper<MarkStudent> lw = wrapper.lambda();
+        lw.eq(MarkStudent::getExamId, examId);
+        lw.eq(MarkStudent::getPaperNumber, paperNumber);
+        return this.count(wrapper);
+    }
+
+    @Override
+    public int getAssignedCount(Long examId, Boolean checked, String courseCode, String coursePaperId,
+                                MarkPaperStatus status, DataPermissionRule dpr) {
+        MarkStudent markStudent = new MarkStudent();
+        markStudent.setExamId(examId);
+        markStudent.setCourseCode(courseCode);
+        markStudent.setCoursePaperId(coursePaperId);
+        markStudent.setAssigned(true);
+        markStudent.setAssignConfirmed(checked);
+        markStudent.setMarkPaperStatus(status.name());
+        return baseMapper.countAssigned(markStudent, dpr);
+    }
+
+    @Override
+    public int countAbsentByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        QueryWrapper<MarkStudent> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkStudent::getExamId, examId).eq(MarkStudent::getPaperNumber, paperNumber)
+                .and(o -> o.eq(MarkStudent::getAbsent, true).or().eq(MarkStudent::getOmrAbsent, true));
+        return this.count(queryWrapper);
+    }
+
+    @Override
+    public List<MarkStudent> listScanCollegeByExamIdAndCourseCodeAndCoursePaperId(Long examId, String courseCode,
+                                                                                  String coursePaperId, String status, DataPermissionRule dpr) {
+        return this.baseMapper.listScanCollegeByExamIdAndCourseCodeAndCoursePaperId(examId, courseCode, coursePaperId,
+                status, dpr);
+    }
+
+    @Override
+    public int countUnexistByExamIdAndPaperNumberAndPaperType(Long examId, String paperNumber, String paperType) {
+        MarkStudent markStudent = new MarkStudent();
+        markStudent.setExamId(examId);
+        markStudent.setPaperNumber(paperNumber);
+        markStudent.setScanStatus(ScanStatus.UNEXIST);
+        markStudent.setPaperType(paperType);
+        markStudent.setMarkPaperStatus(MarkPaperStatus.FORMAL.name());
+        return baseMapper.selectCountByQuery(markStudent, null);
+    }
+
+    @Override
+    public void updateStudentAnswer(Long studentId) {
+        List<String> objectiveAnswers = new ArrayList<>();
+        MarkStudent student = this.getById(studentId);
+        List<ScanStudentPaper> studentPaperList = studentPaperService.findByStudentId(studentId);
+        for (ScanStudentPaper studentPaper : studentPaperList) {
+            List<ScanPaperPage> scanPaperPages = scanPaperPageService.listByPaperId(studentPaper.getPaperId());
+            for (ScanPaperPage scanPaperPage : scanPaperPages) {
+                if (scanPaperPage.getQuestion() != null
+                        && CollectionUtils.isNotEmpty(scanPaperPage.getQuestion().getResult())) {
+                    for (String s : scanPaperPage.getQuestion().getResult()) {
+                        if (s.startsWith("?")) {
+                            s = s.replace("?", "");
+                        }
+                        objectiveAnswers.add(s);
+                    }
+                }
+            }
+        }
+        student.setAnswers(JSON.toJSONString(objectiveAnswers));
+        this.saveOrUpdate(student);
+        // 客观题统分
+        this.calculateObjectiveScore(student);
+    }
+
+    @Override
+    public List<MarkStudentScoreVo> listMarkStudentScoreList(Long examId, String paperNumber) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        if (!MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
+            throw ExceptionResultEnum.ERROR.exception("科目未结束评卷,不能同步考生成绩");
+        }
+
+        List<MarkStudent> markStudentList = this.listByExamIdAndPaperNumberAndNotAbsent(examId, paperNumber);
+        List<MarkQuestion> objectiveQuestionList = markQuestionService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, null, true);
+        List<MarkQuestion> subjectiveQuestionList = markQuestionService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, null, false);
+        List<MarkStudentScoreVo> markStudentScoreVoList = new ArrayList<>();
+        for (MarkStudent markStudent : markStudentList) {
+            MarkStudentScoreVo markStudentScoreVo = new MarkStudentScoreVo();
+            markStudentScoreVo.setExamId(markStudent.getExamId());
+            markStudentScoreVo.setCourseCode(markStudent.getCourseCode());
+            markStudentScoreVo.setCourseName(markStudent.getCourseName());
+            markStudentScoreVo.setPaperNumber(markStudent.getPaperNumber());
+            markStudentScoreVo.setStudentCode(markStudent.getStudentCode());
+            markStudentScoreVo.setStudentName(markStudent.getStudentName());
+            // 客观题
+            markStudentScoreVo.setObjectiveScore(markStudent.getObjectiveScore());
+            markStudentScoreVo.setObjectiveScoreList(markStudent.getScoreList(true, objectiveQuestionList));
+            // 主观题
+            markStudentScoreVo.setSubjectiveScore(markStudent.getSubjectiveScore());
+            markStudentScoreVo.setSubjectiveScoreList(markStudent.getScoreList(false, subjectiveQuestionList));
+
+            // 总分
+            markStudentScoreVo.setTotalScore(markStudent.getTotalScore());
+            List<ScoreItem> totalScoreItemList = new ArrayList<>();
+            totalScoreItemList.addAll(markStudentScoreVo.getObjectiveScoreList());
+            totalScoreItemList.addAll(markStudentScoreVo.getSubjectiveScoreList());
+            totalScoreItemList.stream().sorted(Comparator.comparing(ScoreItem::getMainNumber).thenComparing(ScoreItem::getSubNumber));
+            markStudentScoreVo.setTotalScoreList(totalScoreItemList);
+            markStudentScoreVoList.add(markStudentScoreVo);
+        }
+        return markStudentScoreVoList;
+    }
+
+    @Override
+    public void trackExport(Long examId, String coursePaperId, HttpServletResponse response) {
+        {
+            List<MarkStudent> list = this.listByExamIdAndCoursePaperId(examId,coursePaperId);
+            if (CollectionUtils.isNotEmpty(list)) {
+                try {
+                    response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("轨迹图.zip","UTF-8"));
+                    ZipWriter writer = ZipWriter.create(response.getOutputStream());
+                    ByteArrayOutputStream os = new ByteArrayOutputStream();
+                    for (MarkStudent s:list) {
+                        if(!s.getUpload()){
+                            continue;
+                        }
+                        List<FilePathVo> vos = JSON.parseArray(s.getSheetPath(), FilePathVo.class);
+                        List<MarkTrack> trackList = markTrackService.listByStudentId(s.getId());
+                        Document document = new Document(new Rectangle(PageSize.A3.getHeight(),PageSize.A3.getWidth()));
+                        // 本地保存目录
+                        File tempFile = SystemConstant.getFileTempParentDirVar(SystemConstant.TEMP_PREFIX);
+                        File file =new File(tempFile.getPath()+File.separator + s.getStudentCode()+".pdf");
+                        PdfWriter pdfWriter = PdfWriter.getInstance(document,new FileOutputStream(file));
+                        document.open();
+                        for (int i = 0; i < vos.size(); i++) {
+                            FilePathVo vo = vos.get(i);
+                            File sheet =  fileUploadService.downloadFile(JSON.toJSONString(vo),vo.getPath());
+                            File track = new File(tempFile.getPath()+File.separator + s.getStudentCode()+"-"+(i+1)+".jpg");
+                            int offsetIndex = i+1;
+                            List<MarkTrack> tracks = trackList.stream().filter(t->t.getOffsetIndex().equals(offsetIndex)).collect(Collectors.toList());
+                            this.createTrack(sheet,track,tracks);
+                            Image image = Image.getInstance(track.getPath());
+                            image.scaleAbsolute(PageSize.A3.getHeight()-100,PageSize.A3.getWidth()-100);
+                            if(i!=0){
+                                document.newPage();
+                            }
+                            document.add(image);
+                            sheet.delete();
+                            track.delete();
+                        }
+                        document.close();
+                        pdfWriter.close();
+                        writer.write(file,s.getStudentCode()+".pdf");
+                    }
+                    writer.close();
+                }catch (Exception e){
+                    e.printStackTrace();
+                    throw new ParameterException("文件下载失败", e);
+                }
+            }
+        }
+    }
+
+    private void createTrack(File sheet, File track, List<MarkTrack> trackList)throws IOException {
+        {
+            FileOutputStream output = null;
+            try {
+                BufferedImage image = ImageIO.read(new FileInputStream(sheet));
+                image = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, null);
+                Graphics2D g = image.createGraphics();// 得到图形上下文
+                g.setColor(Color.red); // 设置画笔颜色
+                // 设置字体
+                g.setFont(new Font("微软雅黑", Font.LAYOUT_LEFT_TO_RIGHT, 30));
+                // 写入签名
+                if (trackList != null && !trackList.isEmpty()) {
+                    for (int i = 0; i < trackList.size(); i++) {
+                        MarkTrack t = trackList.get(i);
+                        BigDecimal left = new BigDecimal(t.getOffsetY());
+                        BigDecimal top = new BigDecimal(t.getOffsetX());
+                        g.drawString(t.getScore().toString(), left.intValue(), top.intValue());
+                    }
+                }
+                g.dispose();
+
+                output = new FileOutputStream(track);
+                ImageIO.write(image, "jpg", output);
+            } catch (Exception e) {
+                log.error(SystemConstant.LOG_ERROR, e);
+            } finally {
+                if (Objects.nonNull(output)) {
+                    output.flush();
+                    output.close();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void sheetExport(Long examId, String coursePaperId, HttpServletResponse response) {
+        List<MarkStudent> list = this.listByExamIdAndCoursePaperId(examId,coursePaperId);
+        if (CollectionUtils.isNotEmpty(list)) {
+            try {
+                response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("原图.zip","UTF-8"));
+                ZipWriter writer = ZipWriter.create(response.getOutputStream());
+                ByteArrayOutputStream os = new ByteArrayOutputStream();
+                for (MarkStudent s:list) {
+                    if(!s.getUpload()){
+                        continue;
+                    }
+                    List<FilePathVo> vos = JSON.parseArray(s.getSheetPath(), FilePathVo.class);
+                    for (int i = 0; i < vos.size(); i++) {
+                        FilePathVo vo = vos.get(i);
+                        File file =  fileUploadService.downloadFile(JSON.toJSONString(vo),vo.getPath());
+                        String format = FilenameUtils.getExtension(file.getName());
+                        writer.write(file, s.getStudentCode()+"-"+(i+1)+"."+format);
+                        file.delete();
+                    }
+                }
+                writer.close();
+            }catch (Exception e){
+                e.printStackTrace();
+                throw new ParameterException("文件下载失败", e);
+            }
+        }
+    }
+}

+ 1 - 1
teachcloud-task/src/main/java/com/qmth/teachcloud/task/job/service/impl/JobServiceImpl.java

@@ -187,7 +187,7 @@ public class JobServiceImpl implements JobService {
     @Override
     public void clearTimeoutTask() {
         try {
-            long timeoutMinute = 60l;
+            long timeoutMinute = 60L;
             TaskLockUtil.clearTimeoutTask(timeoutMinute * 60 * 1000);
         } catch (Exception e) {
             log.error("CronCleanTask error", e);