Browse Source

3.4.4 update-20250321,bug修改

xiaofei 3 months ago
parent
commit
3b803f6074
21 changed files with 767 additions and 34 deletions
  1. 1 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/ExamCardServiceImpl.java
  2. 1 1
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/TBVersionServiceImpl.java
  3. 16 0
      distributed-print/install/mysql/upgrade/3.4.4.sql
  4. 0 4
      distributed-print/pom.xml
  5. 4 1
      distributed-print/src/main/java/com/qmth/distributed/print/api/mark/MarkTaskController.java
  6. 407 0
      distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_4.java
  7. 4 0
      teachcloud-common/pom.xml
  8. 1 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/enums/ExceptionResultEnum.java
  9. 1 1
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/impl/AuthInfoServiceImpl.java
  10. 137 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/entity/MarkArchiveStudent.java
  11. 1 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/mapper/MarkStudentMapper.java
  12. 16 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkArchiveStudentService.java
  13. 1 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkStudentService.java
  14. 20 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkArchiveStudentServiceImpl.java
  15. 4 3
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkDocumentServiceImpl.java
  16. 9 11
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkPaperServiceImpl.java
  17. 11 10
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkQuestionServiceImpl.java
  18. 91 3
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkServiceImpl.java
  19. 8 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkStudentServiceImpl.java
  20. 19 0
      teachcloud-mark/src/main/resources/mapper/MarkArchiveStudentMapper.xml
  21. 15 0
      teachcloud-mark/src/main/resources/mapper/MarkStudentMapper.xml

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

@@ -323,6 +323,7 @@ public class ExamCardServiceImpl extends ServiceImpl<ExamCardMapper, ExamCard> i
                 }
                 String htmlContent = teachcloudCommonService.readFileContent(attachment.getPath());
                 examCard.setHtmlContent(htmlContent);
+                examCard.setPageSize(PageSizeEnum.A3.name());
             }
             // 方式为自定义
             else if (CardCreateMethodEnum.STANDARD.equals(params.getCreateMethod())

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

@@ -48,7 +48,7 @@ public class TBVersionServiceImpl extends ServiceImpl<TBVersionMapper, TBVersion
     public void validVersion(String version) {
         TBVersion tbVersion = this.getByClientVersion(version);
         if (tbVersion == null || !StringUtils.equalsAny(SystemConstant.VERSION_VALUE, tbVersion.listSoftVersion().toArray(new String[0]))) {
-            throw ExceptionResultEnum.ERROR.exception("客户端版本号[" + version + "],无法使用当前服务端,请联系管理员处理");
+            throw ExceptionResultEnum.CLIENT_VERSION_ERROR.exception("客户端版本号[" + version + "],无法使用当前服务端,请联系管理员处理");
         }
     }
 }

+ 16 - 0
distributed-print/install/mysql/upgrade/3.4.4.sql

@@ -336,3 +336,19 @@ UPDATE `sys_privilege` SET `related` = '490,543,955,959,1139,1220' WHERE (`id` =
 UPDATE `sys_privilege` SET `related` = '542,959,1139,1220' WHERE (`id` = '498');
 UPDATE `sys_privilege` SET `related` = '542,959,1139,1220' WHERE (`id` = '499');
 UPDATE `sys_privilege` SET `related` = '542,959,1139,1220' WHERE (`id` = '500');
+
+-- 2025-03-21
+CREATE TABLE `mark_archive_student` (
+           `student_id` BIGINT(20) NOT NULL,
+           `exam_id` BIGINT(20) NULL,
+           `paper_number` VARCHAR(100) NULL,
+           `student_code` VARCHAR(45) NULL,
+           `basic_student_id` BIGINT(20) NULL,
+           `sheet_urls` MEDIUMTEXT NULL COMMENT '图片地址',
+           `subjective_questions` MEDIUMTEXT NULL,
+           `objective_questions` MEDIUMTEXT NULL,
+           `card_content` MEDIUMTEXT NULL,
+           `create_time` BIGINT(20) NULL,
+           PRIMARY KEY (`student_id`))
+    COMMENT = '考生归档数据表';
+

+ 0 - 4
distributed-print/pom.xml

@@ -44,10 +44,6 @@
             <groupId>com.qmth.teachcloud.data</groupId>
             <artifactId>teachcloud-data</artifactId>
         </dependency>
-<!--        <dependency>-->
-<!--            <groupId>com.qmth.boot</groupId>-->
-<!--            <artifactId>data-upgrade</artifactId>-->
-<!--        </dependency>-->
         <dependency>
             <groupId>com.qmth.teachcloud.common.api</groupId>
             <artifactId>teachcloud-common-api</artifactId>

+ 4 - 1
distributed-print/src/main/java/com/qmth/distributed/print/api/mark/MarkTaskController.java

@@ -8,6 +8,7 @@ import com.qmth.teachcloud.common.util.Result;
 import com.qmth.teachcloud.common.util.ResultUtil;
 import com.qmth.teachcloud.mark.dto.mark.manage.MarkRejectHistoryDto;
 import com.qmth.teachcloud.mark.dto.mark.manage.MarkTaskDto;
+import com.qmth.teachcloud.mark.entity.MarkTask;
 import com.qmth.teachcloud.mark.enums.MarkTaskStatus;
 import com.qmth.teachcloud.mark.service.MarkRejectHistoryService;
 import com.qmth.teachcloud.mark.service.MarkTaskService;
@@ -34,7 +35,7 @@ import javax.validation.constraints.Min;
 @Api(tags = "评卷-任务管理")
 @RestController
 @RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + SystemConstant.PREFIX_URL_MARK + "/task")
-public class MarkTaskController {
+public class MarkTaskController extends BaseController {
 
     @Resource
     private MarkTaskService markTaskService;
@@ -75,6 +76,8 @@ public class MarkTaskController {
     @RequestMapping(value = "/reject", method = RequestMethod.POST)
     public Result reject(@ApiParam(value = "任务ID", required = true) @RequestParam Long id,
                          @ApiParam(value = "打回原因", required = true) @RequestParam String rejectReason) {
+        MarkTask markTask = markTaskService.getById(id);
+        validMarkPaperForMark(markTask.getExamId(), markTask.getPaperNumber());
         markTaskService.rejectMarkTask(id, rejectReason);
         return ResultUtil.ok(true);
     }

+ 407 - 0
distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_4.java

@@ -0,0 +1,407 @@
+package com.qmth.distributed.print.upgrade;
+
+import com.alibaba.fastjson.JSON;
+import com.qmth.boot.data.upgrade.annotation.DataUpgradeVersion;
+import com.qmth.boot.data.upgrade.service.DataUpgradeService;
+import com.qmth.boot.data.upgrade.utils.ResourceFileHelper;
+import com.qmth.teachcloud.common.bean.dto.mark.PictureConfig;
+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.mark.dto.mark.MarkQuestionAnswerVo;
+import com.qmth.teachcloud.mark.dto.mark.manage.MarkerScoreDTO;
+import com.qmth.teachcloud.mark.dto.mark.manage.TaskQuestion;
+import com.qmth.teachcloud.mark.dto.mark.manage.TrackDTO;
+import com.qmth.teachcloud.mark.dto.mark.score.SheetUrlDto;
+import com.qmth.teachcloud.mark.dto.mark.score.StudentObjectiveAnswerDto;
+import com.qmth.teachcloud.mark.dto.mark.score.StudentPaperDetailDto;
+import com.qmth.teachcloud.mark.entity.*;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@DataUpgradeVersion("3.4.4")
+public class DataUpgrade_3_4_4 implements DataUpgradeService {
+
+    private static final Logger log = LoggerFactory.getLogger(DataUpgrade_3_4_4.class);
+
+    // 一页1000条
+    private static int PAGE_SIZE = 500;
+
+    @Override
+    public void process(JdbcTemplate jdbcTemplate) {
+        log.info("process...");
+        // 执行升级脚本
+//        this.execUpgradeSql(jdbcTemplate);
+
+        // 执行历史数据归档
+//        this.execDataArchive(jdbcTemplate);
+
+    }
+
+    private void execDataArchive(JdbcTemplate jdbcTemplate) {
+        // 用户数据
+        Map<Long, SysUser> sysUserMap = this.mapSysUserSql(jdbcTemplate);
+
+        List<MarkPaper> markPaperList = this.listMarkPaper(jdbcTemplate);
+        for (MarkPaper markPaper : markPaperList) {
+            Long examId = markPaper.getExamId();
+            String paperNumber = markPaper.getPaperNumber();
+            List<MarkStudent> markStudentList = this.listMarkStudent(jdbcTemplate, examId, paperNumber);
+            // 客观题数据
+            Map<String, List<MarkQuestionAnswerVo>> objectiveQuestionMap = this.mapObjectiveMarkQuestion(jdbcTemplate, examId, paperNumber);
+            // 主观题数据
+            List<MarkQuestion> markQuestionList = this.listSubjectiveMarkQuestionSql(jdbcTemplate, examId, paperNumber);
+
+            for (MarkStudent markStudent : markStudentList) {
+                MarkArchiveStudent markArchiveStudent = new MarkArchiveStudent();
+                markArchiveStudent.setStudentId(markStudent.getId());
+                markArchiveStudent.setExamId(examId);
+                markArchiveStudent.setPaperNumber(paperNumber);
+                markArchiveStudent.setBasicStudentId(markStudent.getBasicStudentId());
+                markArchiveStudent.setStudentCode(markStudent.getStudentCode());
+                markArchiveStudent.setSheetUrls(buildSheetUrls(jdbcTemplate, markStudent));
+                markArchiveStudent.setSubjectiveQuestions(buildSubjectiveQuestions(jdbcTemplate, markStudent, markQuestionList, sysUserMap));
+                markArchiveStudent.setObjectiveQuestions(buildObjectiveQuestions(objectiveQuestionMap, markStudent));
+                markArchiveStudent.setCardContent(getCardContent(jdbcTemplate, markStudent));
+                markArchiveStudent.setCreateTime(System.currentTimeMillis());
+            }
+        }
+    }
+
+    /**
+     * 电子卡格式信息
+     */
+    private String getCardContent(JdbcTemplate jdbcTemplate, MarkStudent markStudent) {
+        return null;
+    }
+
+    /**
+     * 客观题信息
+     */
+    private String buildObjectiveQuestions(Map<String, List<MarkQuestionAnswerVo>> objectiveQuestionMap, MarkStudent markStudent) {
+        List<String> answers = markStudent.getAnswerList();
+        List<String> answerScores = markStudent.getAnswerScoreList();
+        List<MarkQuestionAnswerVo> questions = objectiveQuestionMap.get(markStudent.getPaperType());
+        int questionCount = questions.size();
+        int answerCount = answers.size();
+        int answerScoreCount = answerScores.size();
+
+        List<StudentObjectiveAnswerDto> answerDtoList = new ArrayList<>();
+        // 已设置客观题
+        int maxCount = Math.max(questionCount, answerCount);
+        for (int i = 0; i < maxCount; i++) {
+            MarkQuestionAnswerVo q = questionCount > i ? questions.get(i) : null;
+            String answer = answerCount > i ? answers.get(i) : "#";
+            String answerScore = answerScoreCount > i ? answerScores.get(i) : "0";
+            StudentObjectiveAnswerDto studentObjectiveAnswerDto = new StudentObjectiveAnswerDto();
+            studentObjectiveAnswerDto.setMainNumber(q != null ? q.getMainNumber() : 0);
+            studentObjectiveAnswerDto.setSubNumber(q != null ? q.getSubNumber() : 0);
+            studentObjectiveAnswerDto.setStandardAnswer(q != null ? q.getAnswer() : null);
+            studentObjectiveAnswerDto.setAnswer(answer);
+            studentObjectiveAnswerDto.setScore(Double.valueOf(answerScore));
+            studentObjectiveAnswerDto.setTotalScore(q != null && q.getTotalScore() != null ? q.getTotalScore() : 0);
+            studentObjectiveAnswerDto.setExist(q != null && q.getTotalScore() != null && q.getTotalScore() > 0);
+            studentObjectiveAnswerDto.setQuestionType(q != null ? q.getQuestionType() : 0);
+            answerDtoList.add(studentObjectiveAnswerDto);
+        }
+        return JSON.toJSONString(answerDtoList);
+    }
+
+    /**
+     * 主观题信息
+     */
+    private String buildSubjectiveQuestions(JdbcTemplate jdbcTemplate, MarkStudent markStudent, List<MarkQuestion> sList, Map<Long, SysUser> sysUserMap) {
+        Long examId = markStudent.getExamId();
+        String paperNumber = markStudent.getPaperNumber();
+        Long studentId = markStudent.getId();
+        List<TaskQuestion> taskQuestions = new ArrayList<>();
+        List<MarkSubjectiveScore> scoreList = this.listMarkSubjectiveScore(jdbcTemplate, studentId);
+        for (int i = 0; i < scoreList.size(); i++) {
+            MarkQuestion question = sList.get(i);
+            TaskQuestion step = buildStep(question);
+            // 管理员复核时,默认全部为自己评
+            step.setSelfMark(true);
+            Double score = scoreList.get(i).getScore();
+            if (!scoreList.isEmpty() && scoreList.size() == sList.size()) {
+                step.setMarkerScore(score);
+            }
+            // 增加阅卷轨迹列表获取
+            List<MarkTask> markTaskList = this.listMarkTask(jdbcTemplate, studentId, question.getId());
+            // 不管单评还是多评显示所有评卷员给分轨迹
+            for (MarkTask markTask : markTaskList) {
+                for (TrackDTO track : markTask.listMarkerTrack()) {
+                    SysUser user = sysUserMap.get(markTask.getUserId());
+                    track.setUserId(markTask.getUserId());
+                    track.setUserName(user.getRealName() + "(" + user.getLoginName() + ")");
+                    step.addMarkTrack(track);
+                }
+
+                if (CollectionUtils.isNotEmpty(markTask.listHeaderTrack()) && markTask.getTaskNumber() == 1) {
+                    for (TrackDTO trackDTO : markTask.listHeaderTrack()) {
+                        SysUser sysUser = sysUserMap.get(markTask.getHeaderId());
+                        if (sysUser != null) {
+                            trackDTO.setUserId(sysUser.getId());
+                            trackDTO.setUserName(sysUser.getRealName() + "(" + sysUser.getLoginName() + ")");
+                        }
+                        trackDTO.setHeaderType(markTask.getStatus());
+                        step.addHeadTrack(trackDTO);
+                    }
+                }
+
+                // 普通模式无轨迹数据。单独处理评卷员每题得分
+                if (CollectionUtils.isEmpty(step.getMarkerTrackList()) && markTask.getMarkerScore() != null) {
+                    // 评卷员
+                    MarkerScoreDTO markerScoreDTO = new MarkerScoreDTO();
+                    markerScoreDTO.setUserId(markTask.getUserId());
+                    SysUser markerUser = sysUserMap.get(markTask.getUserId());
+                    if (markerUser != null) {
+                        markerScoreDTO.setLoginName(markerUser.getLoginName());
+                        markerScoreDTO.setUserName(markerUser.getRealName());
+                    }
+                    markerScoreDTO.setScore(score);
+                    markerScoreDTO.setHeader(false);
+                    step.addMarkerList(markerScoreDTO);
+
+                    // 科组长
+                    if (CollectionUtils.isEmpty(markTask.listHeaderTrack()) && markTask.getTaskNumber() == 1) {
+                        if (!markTask.getMarkerScore().equals(score)) {
+                            MarkerScoreDTO headerScoreDTO = new MarkerScoreDTO();
+                            headerScoreDTO.setUserId(markTask.getHeaderId());
+                            SysUser headerUser = sysUserMap.get(markTask.getHeaderId());
+                            if (headerUser != null) {
+                                headerScoreDTO.setLoginName(headerUser.getLoginName());
+                                headerScoreDTO.setUserName(headerUser.getRealName());
+                            }
+                            headerScoreDTO.setScore(score);
+                            headerScoreDTO.setHeader(true);
+                            headerScoreDTO.setHeaderType(markTask.getStatus());
+                            step.addMarkerList(headerScoreDTO);
+                        }
+                    }
+                }
+            }
+            taskQuestions.add(step);
+        }
+        return JSON.toJSONString(taskQuestions);
+    }
+
+    private TaskQuestion buildStep(MarkQuestion question) {
+        TaskQuestion step = new TaskQuestion();
+        step.setMainNumber(question.getMainNumber());
+        step.setSubNumber(question.getSubNumber());
+        step.setQuestionType(question.getQuestionType());
+        step.setPicList(StringUtils.isNotBlank(question.getPicList()) ? JSON.parseArray(question.getPicList(), PictureConfig.class) : null);
+        step.setQuestionId(question.getId());
+        step.setTitle(question.getMainTitle());
+        step.setDefaultScore(0d);
+        step.setMaxScore(question.getTotalScore());
+        step.setMinScore(0d);
+        step.setIntervalScore(question.getIntervalScore());
+        step.setUncalculate(false);
+        return step;
+    }
+
+    /**
+     * 图片信息
+     */
+    private String buildSheetUrls(JdbcTemplate jdbcTemplate, MarkStudent markStudent) {
+        // 原图
+        List<SheetUrlDto> sheetUrls = new ArrayList<>();
+        List<StudentPaperDetailDto> studentPaperDetailDtoList = this.listData(jdbcTemplate, getSheetUrlsSql(markStudent.getId()), StudentPaperDetailDto.class);
+        for (int i = 0; i < studentPaperDetailDtoList.size(); i++) {
+            StudentPaperDetailDto studentPaperDetailDto = studentPaperDetailDtoList.get(i);
+            sheetUrls.add(new SheetUrlDto(
+                    2 * (studentPaperDetailDto.getPaperIndex() - 1) + studentPaperDetailDto.getPageIndex(),
+                    studentPaperDetailDto.getSheetPath(),
+                    studentPaperDetailDto.getRecogData()));
+        }
+        return JSON.toJSONString(sheetUrls);
+    }
+
+    private void execUpgradeSql(JdbcTemplate jdbcTemplate) {
+        String rootPath = System.getProperty("user.dir");
+        StringJoiner sj = new StringJoiner(File.separator);
+        sj.add(rootPath).add("distributed-print").add("install").add("mysql").add("upgrade").add("3.4.4.sql");
+        try {
+            FileInputStream inputStream = new FileInputStream(sj.toString());
+            String[] sqlList = StringUtils.split(ResourceFileHelper.readContent(inputStream), ";");
+            Arrays.stream(sqlList).filter(StringUtils::isNotBlank).forEach(sql -> {
+                log.info(sql);
+                jdbcTemplate.execute(sql);
+            });
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    private <T> List<T> listData(JdbcTemplate jdbcTemplate, String sql, Class<T> tClass) {
+        //将查询的语句封装到List集合中,集合中存储每一个员工实体
+        List<T> list = new ArrayList<>();
+        try {
+            list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(tClass));
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception("查询失败:" + e.getMessage());
+        }
+        return list;
+    }
+
+    private <T> List<T> pageData(JdbcTemplate jdbcTemplate, String sql, Class<T> tClass) {
+        if (!sql.contains("limit")) {
+            sql += " limit ?,?";
+        }
+        //将查询的语句封装到List集合中,集合中存储每一个员工实体
+        List<T> listAll = new ArrayList<>();
+        try {
+            List<T> list;
+            int pageNumber = 0;
+            do {
+                int offset = pageNumber * PAGE_SIZE + PAGE_SIZE;
+                list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(tClass), pageNumber, offset);
+                if (CollectionUtils.isNotEmpty(list)) {
+                    listAll.addAll(list);
+                    pageNumber++;
+                }
+            } while (CollectionUtils.isNotEmpty(list));
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception("查询失败:" + e.getMessage());
+        }
+        return listAll;
+    }
+
+    private <T> List<T> pageData(JdbcTemplate jdbcTemplate, String sql, Class<T> tClass, int pageNumber) {
+        //将查询的语句封装到List集合中,集合中存储每一个员工实体
+        List<T> listAll = new ArrayList<>();
+        try {
+            int offset = pageNumber * PAGE_SIZE + PAGE_SIZE;
+            List<T> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(tClass), pageNumber, offset);
+            if (CollectionUtils.isNotEmpty(list)) {
+                listAll.addAll(list);
+            }
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception("查询失败:" + e.getMessage());
+        }
+        return listAll;
+    }
+
+    private String getSheetUrlsSql(Long studentId) {
+        StringBuffer sql = new StringBuffer();
+        sql.append("SELECT                                                      ");
+        sql.append("ssp.student_id studentId,                                   ");
+        sql.append("ssp.paper_index paperIndex,                                 ");
+        sql.append("ssp.paper_id paperId,                                       ");
+        sql.append("sp.card_number cardNumber,                                  ");
+        sql.append("spp.page_index pageIndex,                                   ");
+        sql.append("spp.sheet_path sheetPath,                                   ");
+        sql.append("spp.recog_data recogData                                    ");
+        sql.append("FROM                                                        ");
+        sql.append("       (SELECT                                              ");
+        sql.append("               student_id, paper_index, paper_id            ");
+        sql.append("                FROM                                        ");
+        sql.append("               scan_student_paper                           ");
+        sql.append("                WHERE                                       ");
+        sql.append("                student_id = " + studentId + ")             ");
+        sql.append("        JOIN                                                ");
+        sql.append("scan_paper sp ON ssp.paper_id = sp.id                       ");
+        sql.append("AND ssp.paper_index = sp.number                             ");
+        sql.append("JOIN                                                        ");
+        sql.append("scan_paper_page spp ON sp.id = spp.paper_id                 ");
+        sql.append("ORDER BY ssp.paper_index , spp.page_index                   ");
+        return sql.toString();
+    }
+
+    private Map<String, List<MarkQuestionAnswerVo>> mapObjectiveMarkQuestion(JdbcTemplate jdbcTemplate, Long examId, String paperNumber) {
+        StringBuffer sql = new StringBuffer();
+        sql.append(" SELECT                                                                     ");
+        sql.append("     mqa.answer,                                                            ");
+        sql.append("     mqa.objective_policy,                                                  ");
+        sql.append("     mqa.objective_policy_score,                                            ");
+        sql.append("     mq.*                                                                   ");
+        sql.append(" FROM                                                                       ");
+        sql.append("     (SELECT                                                                ");
+        sql.append("         *                                                                  ");
+        sql.append("     FROM                                                                   ");
+        sql.append("         mark_question                                                      ");
+        sql.append("     WHERE                                                                  ");
+        sql.append("         exam_id = " + examId + " AND paper_number = " + paperNumber + "    ");
+        sql.append("             AND objective = TRUE) mq                                       ");
+        sql.append("         LEFT JOIN                                                          ");
+        sql.append("     (SELECT                                                                ");
+        sql.append("         *                                                                  ");
+        sql.append("     FROM                                                                   ");
+        sql.append("         mark_question_answer                                               ");
+        sql.append("     WHERE                                                                  ");
+        sql.append("         exam_id = " + examId + " AND paper_number = " + paperNumber + ") mqa    ");
+        sql.append("       ON mq.main_number = mqa.main_number                                  ");
+        sql.append("         AND mq.sub_number = mqa.sub_number                                 ");
+        sql.append(" ORDER BY mq.main_number , mq.sub_number                                    ");
+
+        List<MarkQuestionAnswerVo> questions = this.listData(jdbcTemplate, sql.toString(), MarkQuestionAnswerVo.class);
+        Map<String, List<MarkQuestionAnswerVo>> objectiveQuestionMap = questions.stream().collect(Collectors.groupingBy(MarkQuestionAnswerVo::getPaperType));
+        return objectiveQuestionMap;
+    }
+
+    private List<MarkQuestion> listSubjectiveMarkQuestionSql(JdbcTemplate jdbcTemplate, Long examId, String paperNumber) {
+        StringBuffer sql = new StringBuffer();
+        sql.append(" SELECT                                                     ");
+        sql.append("     *                                                      ");
+        sql.append(" FROM                                                       ");
+        sql.append("     mark_question mq                                       ");
+        sql.append(" WHERE                                                      ");
+        sql.append("     mq.exam_id = " + examId + " AND mq.paper_number = " + paperNumber + "                 ");
+        sql.append("         AND mq.objective = FALSE                           ");
+        sql.append(" ORDER BY mq.main_number , mq.sub_number                    ");
+        return this.listData(jdbcTemplate, sql.toString(), MarkQuestion.class);
+    }
+
+    private List<MarkSubjectiveScore> listMarkSubjectiveScore(JdbcTemplate jdbcTemplate, Long studentId) {
+        StringBuffer sql = new StringBuffer();
+        sql.append(" SELECT                                                         ");
+        sql.append("     *                                                          ");
+        sql.append(" FROM                                                           ");
+        sql.append("     mark_subjective_score mss                                  ");
+        sql.append(" WHERE                                                          ");
+        sql.append("     mss.student_id = " + studentId + "                         ");
+        sql.append(" ORDER BY mss.main_number , mss.sub_number                      ");
+        return this.listData(jdbcTemplate, sql.toString(), MarkSubjectiveScore.class);
+    }
+
+    private List<MarkTask> listMarkTask(JdbcTemplate jdbcTemplate, Long studentId, Long questionId) {
+        StringBuffer sql = new StringBuffer();
+        sql.append(" SELECT                                                             ");
+        sql.append("     *                                                              ");
+        sql.append(" FROM                                                               ");
+        sql.append("     mark_task mt                                                   ");
+        sql.append(" WHERE                                                              ");
+        sql.append("     mt.student_id = " + studentId + " AND mt.question_id = " + questionId + "  ");
+        return this.listData(jdbcTemplate, sql.toString(), MarkTask.class);
+    }
+
+    private Map<Long, SysUser> mapSysUserSql(JdbcTemplate jdbcTemplate) {
+        String sql = "select * from sys_user";
+        List<SysUser> sysUserList = this.listData(jdbcTemplate, sql, SysUser.class);
+        Map<Long, SysUser> sysUserMap = sysUserList.stream().collect(Collectors.toMap(m -> m.getId(), Function.identity()));
+        return sysUserMap;
+    }
+
+    private List<MarkPaper> listMarkPaper(JdbcTemplate jdbcTemplate) {
+        String sql = "select * from mark_paper where status = 'FINISH'";
+        return this.listData(jdbcTemplate, sql, MarkPaper.class);
+    }
+
+    private List<MarkStudent> listMarkStudent(JdbcTemplate jdbcTemplate, Long examId, String paperNumber) {
+        String sql = "select * from mark_student where exam_id = " + examId + " and paper_number = '" + paperNumber + "'";
+        return this.listData(jdbcTemplate, sql, MarkStudent.class);
+    }
+}

+ 4 - 0
teachcloud-common/pom.xml

@@ -58,6 +58,10 @@
             <groupId>com.qmth.boot</groupId>
             <artifactId>tools-poi</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>data-upgrade</artifactId>
+        </dependency>
         <dependency>
             <groupId>io.netty</groupId>
             <artifactId>netty-all</artifactId>

+ 1 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/enums/ExceptionResultEnum.java

@@ -96,6 +96,7 @@ public enum ExceptionResultEnum {
     PAPER_STRUCT_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, 5100003, "试卷结构不一致"),
     MARK_PAPER_FINISH(HttpStatus.INTERNAL_SERVER_ERROR, 5000034, "科目已结束评卷"),
     MARK_PAPER_OUT_MARK_TIME(HttpStatus.INTERNAL_SERVER_ERROR, 5000035, "不在评卷时间范围"),
+    CLIENT_VERSION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 5100000, ""),
 
     /**
      * 401

+ 1 - 1
teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/impl/AuthInfoServiceImpl.java

@@ -117,7 +117,7 @@ public class AuthInfoServiceImpl implements AuthInfoService {
         if ((Objects.nonNull(basicSchool) && Objects.nonNull(basicSchool.getEnable()) && !basicSchool.getEnable())
                 || (Objects.isNull(solarService.getAppInfo()) || solarService.getAppInfo().getControl().hasExpired())
                 || (Objects.isNull(solarService.getAppControl()) || solarService.getAppControl().hasExpired())) {
-//            throw ExceptionResultEnum.AUTH_INFO_ERROR.exception();
+            throw ExceptionResultEnum.AUTH_INFO_ERROR.exception();
         }
     }
 

+ 137 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/entity/MarkArchiveStudent.java

@@ -0,0 +1,137 @@
+package com.qmth.teachcloud.mark.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.io.Serializable;
+import java.util.List;
+
+import com.qmth.teachcloud.mark.dto.mark.manage.TaskQuestion;
+import com.qmth.teachcloud.mark.dto.mark.score.SheetUrlDto;
+import com.qmth.teachcloud.mark.dto.mark.score.StudentObjectiveAnswerDto;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * <p>
+ * 考生归档数据表
+ * </p>
+ *
+ * @author xf
+ * @since 2025-03-21
+ */
+@TableName("mark_archive_student")
+@ApiModel(value="MarkArchiveStudent对象", description="考生归档数据表")
+public class MarkArchiveStudent implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "student_id", type = IdType.INPUT)
+    private Long studentId;
+
+    private Long examId;
+
+    private String paperNumber;
+
+    private String studentCode;
+
+    private Long basicStudentId;
+
+    @ApiModelProperty(value = "图片地址")
+    private String sheetUrls;
+
+    private String subjectiveQuestions;
+
+    private String objectiveQuestions;
+
+    private String cardContent;
+
+    private Long createTime;
+
+    public Long getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(Long studentId) {
+        this.studentId = studentId;
+    }
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+    public String getPaperNumber() {
+        return paperNumber;
+    }
+
+    public void setPaperNumber(String paperNumber) {
+        this.paperNumber = paperNumber;
+    }
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+    public Long getBasicStudentId() {
+        return basicStudentId;
+    }
+
+    public void setBasicStudentId(Long basicStudentId) {
+        this.basicStudentId = basicStudentId;
+    }
+    public String getSheetUrls() {
+        return sheetUrls;
+    }
+
+    public void setSheetUrls(String sheetUrls) {
+        this.sheetUrls = sheetUrls;
+    }
+    public String getSubjectiveQuestions() {
+        return subjectiveQuestions;
+    }
+
+    public void setSubjectiveQuestions(String subjectiveQuestions) {
+        this.subjectiveQuestions = subjectiveQuestions;
+    }
+    public String getObjectiveQuestions() {
+        return objectiveQuestions;
+    }
+
+    public void setObjectiveQuestions(String objectiveQuestions) {
+        this.objectiveQuestions = objectiveQuestions;
+    }
+    public String getCardContent() {
+        return cardContent;
+    }
+
+    public void setCardContent(String cardContent) {
+        this.cardContent = cardContent;
+    }
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+
+    @Override
+    public String toString() {
+        return "MarkArchiveStudent{" +
+            "studentId=" + studentId +
+            ", examId=" + examId +
+            ", paperNumber=" + paperNumber +
+            ", studentCode=" + studentCode +
+            ", basicStudentId=" + basicStudentId +
+            ", sheetUrls=" + sheetUrls +
+            ", subjectiveQuestions=" + subjectiveQuestions +
+            ", objectiveQuestions=" + objectiveQuestions +
+            ", cardContent=" + cardContent +
+            ", createTime=" + createTime +
+        "}";
+    }
+}

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

@@ -79,6 +79,7 @@ public interface MarkStudentMapper extends BaseMapper<MarkStudent> {
     List<MarkStudent> listAbsentOrBreachMarkTaskStudent(@Param("examId") Long examId, @Param("paperNumber") String paperNumber);
 
     IPage<MarkStudent> listUnMarkTaskStudent(@Param("page") Page<MarkStudent> page, @Param("examId") Long examId, @Param("paperNumber") String paperNumber, @Param("questionId") Long questionId);
+    IPage<MarkStudent> listUnMarkDoubleTaskStudent(@Param("page") Page<MarkStudent> page, @Param("examId") Long examId, @Param("paperNumber") String paperNumber, @Param("questionId") Long questionId);
 
     StudentVo findOne(@Param("query") StudentQuery query);
 

+ 16 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkArchiveStudentService.java

@@ -0,0 +1,16 @@
+package com.qmth.teachcloud.mark.service;
+
+import com.qmth.teachcloud.mark.entity.MarkArchiveStudent;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 考生归档数据表 服务类
+ * </p>
+ *
+ * @author xf
+ * @since 2025-03-21
+ */
+public interface MarkArchiveStudentService extends IService<MarkArchiveStudent> {
+
+}

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

@@ -139,6 +139,7 @@ public interface MarkStudentService extends IService<MarkStudent> {
     List<MarkStudent> listAbsentOrBreachMarkTaskStudent(Long examId, String paperNumber);
 
     List<MarkStudent> listUnMarkTaskStudent(Long examId, String paperNumber, Long questionId, int pageSize);
+    List<MarkStudent> listUnMarkDoubleTaskStudent(Long examId, String paperNumber, Long questionId, int pageSize);
 
     void calculateObjectiveScore(MarkStudent student);
 

+ 20 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkArchiveStudentServiceImpl.java

@@ -0,0 +1,20 @@
+package com.qmth.teachcloud.mark.service.impl;
+
+import com.qmth.teachcloud.mark.entity.MarkArchiveStudent;
+import com.qmth.teachcloud.mark.mapper.MarkArchiveStudentMapper;
+import com.qmth.teachcloud.mark.service.MarkArchiveStudentService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 考生归档数据表 服务实现类
+ * </p>
+ *
+ * @author xf
+ * @since 2025-03-21
+ */
+@Service
+public class MarkArchiveStudentServiceImpl extends ServiceImpl<MarkArchiveStudentMapper, MarkArchiveStudent> implements MarkArchiveStudentService {
+
+}

+ 4 - 3
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkDocumentServiceImpl.java

@@ -17,6 +17,7 @@ import com.qmth.teachcloud.common.enums.mark.MarkPaperStatus;
 import com.qmth.teachcloud.common.service.FileUploadService;
 import com.qmth.teachcloud.common.service.TeachcloudCommonService;
 import com.qmth.teachcloud.mark.bean.archivescore.ArchiveStudentQuery;
+import com.qmth.teachcloud.mark.dto.mark.MarkPaperFileDto;
 import com.qmth.teachcloud.mark.entity.MarkDocument;
 import com.qmth.teachcloud.mark.entity.MarkPaper;
 import com.qmth.teachcloud.mark.entity.ScanPackage;
@@ -76,11 +77,11 @@ public class MarkDocumentServiceImpl extends ServiceImpl<MarkDocumentMapper, Mar
         list.sort(Comparator.comparingInt(d -> d.getType().getValue()));
         for (MarkDocument d : list) {
             //
-            if (DocumentType.PAPER.equals(d.getType())) {
+            if (DocumentType.PAPER.equals(d.getType()) || DocumentType.ANSWER.equals(d.getType())) {
                 List<String> urlList = new ArrayList<>();
                 if (StringUtils.isNotBlank(d.getFilePath())) {
-                    for (String s : JSON.parseArray(d.getFilePath(), String.class)) {
-                        urlList.add(teachcloudCommonService.filePreview(s));
+                    for (MarkPaperFileDto s : JSON.parseArray(d.getFilePath(), MarkPaperFileDto.class)) {
+                        urlList.add(teachcloudCommonService.filePreview(JSON.toJSONString(s.getFilePathVo())));
                     }
                     d.setFilePath(JSON.toJSONString(urlList));
                 }

+ 9 - 11
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkPaperServiceImpl.java

@@ -46,11 +46,9 @@ import com.qmth.teachcloud.mark.dto.mark.MarkQuestionAnswerVo;
 import com.qmth.teachcloud.mark.dto.mark.score.CheckScoreListDto;
 import com.qmth.teachcloud.mark.dto.mark.score.MarkPaperPackageDto;
 import com.qmth.teachcloud.mark.dto.mark.score.SettingDto;
-import com.qmth.teachcloud.mark.entity.MarkPaper;
-import com.qmth.teachcloud.mark.entity.MarkQuestionAnswer;
-import com.qmth.teachcloud.mark.entity.ScanAnswerCard;
-import com.qmth.teachcloud.mark.entity.ScanPackage;
+import com.qmth.teachcloud.mark.entity.*;
 import com.qmth.teachcloud.mark.enums.CardSource;
+import com.qmth.teachcloud.mark.enums.MarkTaskStatus;
 import com.qmth.teachcloud.mark.enums.OmrTaskStatus;
 import com.qmth.teachcloud.mark.enums.PaperTypeCheckStatus;
 import com.qmth.teachcloud.mark.mapper.MarkPaperMapper;
@@ -107,7 +105,7 @@ public class MarkPaperServiceImpl extends ServiceImpl<MarkPaperMapper, MarkPaper
     @Resource
     private MarkStudentService markStudentService;
     @Resource
-    private MarkUserQuestionService markUserQuestionService;
+    private MarkTaskService markTaskService;
     @Resource
     private MarkUserPaperService markUserPaperService;
     @Resource
@@ -244,15 +242,15 @@ public class MarkPaperServiceImpl extends ServiceImpl<MarkPaperMapper, MarkPaper
                 List<MarkQuestion> markQuestionSubjectiveList = markQuestionService.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
                 // 没有主观题,不校验考生评卷
                 if (CollectionUtils.isNotEmpty(markQuestionSubjectiveList)) {
-//                    if (markQuestionSubjectiveList.stream().filter(m -> m.getGroupNumber() == null).count() > 0) {
-//                        stringJoiner.add(courseInfo + "主观题未全部分组,无法结束评卷");
-//                        continue;
-//                    }
-                    // 未缺考、未违纪且已上传图片的考生全部评完
-                    if (markStudentService.countByExamIdAndPaperNumberAndMarkStatus(examId, paperNumber, SubjectiveStatus.UNMARK) > 0) {
+                    if (markTaskService.countByExamIdAndPaperNumberAndStatusIn(examId, paperNumber, Arrays.asList(MarkTaskStatus.WAITING, MarkTaskStatus.WAIT_ARBITRATE, MarkTaskStatus.REJECTED, MarkTaskStatus.PROBLEM)) > 0) {
                         stringJoiner.add(courseInfo + "考生未全部评完,无法结束评卷");
                         continue;
                     }
+                    // 未缺考、未违纪且已上传图片的考生全部评完
+                    else if (markStudentService.countByExamIdAndPaperNumberAndMarkStatus(examId, paperNumber, SubjectiveStatus.UNMARK) > 0) {
+                        stringJoiner.add(courseInfo + "考生正在统分中,无法结束评卷");
+                        continue;
+                    }
                     // 未全部扫描,不能结束
                     if (markStudentService.countUnexistByExamIdAndPaperNumber(examId, paperNumber) > 0) {
                         stringJoiner.add(courseInfo + "有考生未扫描未核对,请在扫描客户端进行确认操作后再结束阅卷");

+ 11 - 10
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkQuestionServiceImpl.java

@@ -32,10 +32,7 @@ import com.qmth.teachcloud.mark.dto.mark.manage.*;
 import com.qmth.teachcloud.mark.dto.mark.setting.MarkGroupTaskDto;
 import com.qmth.teachcloud.mark.dto.mark.setting.MarkQuestionDto;
 import com.qmth.teachcloud.mark.dto.mark.setting.MarkQuestionSubjectiveStepStatusDto;
-import com.qmth.teachcloud.mark.entity.MarkPaper;
-import com.qmth.teachcloud.mark.entity.MarkQuestionAnswer;
-import com.qmth.teachcloud.mark.entity.MarkTask;
-import com.qmth.teachcloud.mark.entity.MarkUserQuestion;
+import com.qmth.teachcloud.mark.entity.*;
 import com.qmth.teachcloud.mark.enums.LockType;
 import com.qmth.teachcloud.mark.enums.MarkTaskStatus;
 import com.qmth.teachcloud.mark.lock.LockService;
@@ -91,7 +88,7 @@ public class MarkQuestionServiceImpl extends ServiceImpl<MarkQuestionMapper, Mar
     @Resource
     private LockService lockService;
     @Resource
-    private MarkSyncService markSyncService;
+    private MarkSubjectiveScoreService markSubjectiveScoreService;
 
     @Override
     public List<MarkQuestion> listQuestionByExamIdAndPaperNumber(Long examId, String paperNumber) {
@@ -745,12 +742,12 @@ public class MarkQuestionServiceImpl extends ServiceImpl<MarkQuestionMapper, Mar
         String paperNumber = doubleMarkParam.getPaperNumber();
         Long questionId = doubleMarkParam.getQuestionId();
         Double doubleRate = doubleMarkParam.getDoubleRate() != null ? doubleMarkParam.getDoubleRate() : 0;
-        List<MarkTaskStatus> markTaskStatuses = Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.WAIT_ARBITRATE, MarkTaskStatus.PROBLEM, MarkTaskStatus.REJECTED, MarkTaskStatus.ARBITRATED);
-        if (markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndStatusIn(examId, paperNumber, questionId, markTaskStatuses) > 0) {
-            throw ExceptionResultEnum.ERROR.exception("该题已开始评卷,不允许修改");
-        }
 
         MarkQuestion markQuestion = this.getById(questionId);
+        List<MarkTaskStatus> markTaskStatuses = Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.WAIT_ARBITRATE, MarkTaskStatus.PROBLEM, MarkTaskStatus.REJECTED, MarkTaskStatus.ARBITRATED);
+        if (markQuestion.getDoubleRate() == 100 && doubleRate == 0 && markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndStatusIn(examId, paperNumber, questionId, markTaskStatuses) > 0) {
+            throw ExceptionResultEnum.ERROR.exception("该题已开始评卷,不允许双评改单评");
+        }
 
         UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
         updateWrapper.lambda().set(MarkQuestion::getDoubleRate, doubleRate)
@@ -764,7 +761,10 @@ public class MarkQuestionServiceImpl extends ServiceImpl<MarkQuestionMapper, Mar
             this.updateMarkedCount(questionId, 0);
             this.updateTaskCount(questionId, 0);
             if (lockService.trylock(LockType.QUESTION_UPDATE, questionId)) {
-                markSyncService.deleteMarkTask(markQuestion);
+                markSubjectiveScoreService.deleteByExamIdAndPaperNumberAndQuestionId(examId, paperNumber, markQuestion.getId());
+                MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+                // 考生主观题重新统分
+                markService.checkStudentSubjectiveScore(examId, markPaper.getCoursePaperId());
             }
         }
 
@@ -799,6 +799,7 @@ public class MarkQuestionServiceImpl extends ServiceImpl<MarkQuestionMapper, Mar
                     MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
                     subjectiveMarkerClass = markPaper != null && !markPaper.getClassMark();
                     if (!subjectiveMarkerClass) {
+                        subjectiveMarkerClass = true;
                         MarkStudentQuery markStudentQuery = new MarkStudentQuery();
                         markStudentQuery.setExamId(examId);
                         markStudentQuery.setPaperNumber(paperNumber);

+ 91 - 3
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkServiceImpl.java

@@ -433,14 +433,18 @@ public class MarkServiceImpl implements MarkService {
             List<MarkQuestion> markQuestionList = markQuestionService.listByExamIdAndPaperNumberAndObjective(markPaper.getExamId(), markPaper.getPaperNumber(), false);
             for (MarkQuestion markQuestion : markQuestionList) {
                 // 生成正评任务
-                buildFormalLibrary(markQuestion);
+                buildFormalTask(markQuestion);
+                if (markQuestion.getDoubleRate() > 0) {
+                    // 补双评任务
+                    fillFormalTask(markQuestion);
+                }
             }
         } finally {
             lockService.unwatch(LockType.EXAM_SUBJECT, markPaper.getExamId(), markPaper.getPaperNumber());
         }
     }
 
-    private void buildFormalLibrary(MarkQuestion markQuestion) {
+    private void buildFormalTask(MarkQuestion markQuestion) {
         int pageSize = 100;
         try {
             lockService.watch(LockType.QUESTION, markQuestion.getId());
@@ -539,6 +543,90 @@ public class MarkServiceImpl implements MarkService {
         }
     }
 
+    private void fillFormalTask(MarkQuestion markQuestion) {
+        int pageSize = 100;
+        try {
+            lockService.watch(LockType.QUESTION, markQuestion.getId());
+            // 上锁后重复验证分组状态
+            MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markQuestion.getExamId(), markQuestion.getPaperNumber());
+            if (MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
+                return;
+            }
+            int count = 0;
+            List<MarkStudent> studentList = markStudentService.listUnMarkDoubleTaskStudent(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), pageSize);
+            while (CollectionUtils.isNotEmpty(studentList)) {
+                // 已生成的双评任务总数的第一组任务数
+                int doubleMarkTaskCount1 = markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndTaskNumber(
+                        markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), 1);
+                // 已生成的双评任务总数的第二组任务数
+                int doubleMarkTaskCount2 = markTaskService.countByExamIdAndPaperNumberAndQuestionIdAndTaskNumber(
+                        markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), 2);
+                List<MarkTask> taskList = new ArrayList<>();
+                int doubleCount = 0;
+                int studentCount = studentList.size();
+                for (MarkStudent student : studentList) {
+                    // 开启双评时需要判断是否生成第二份评卷任务
+                    if (markQuestion.getDoubleRate() != null && markQuestion.getDoubleRate() > 0) {
+                        boolean needDouble;
+                        if (markQuestion.getDoubleRate() == 100) {
+                            needDouble = true;
+                        } else {
+                            double libraryCount = taskList.size();
+                            int expectCount = (int) ((doubleMarkTaskCount1 + studentCount) * markQuestion.getDoubleRate()
+                                    / 100);
+                            // 随机数判断加入当前已经生成双评任务的比例加权
+                            // 实际双评任务数小于理论生成数 &&(剩余未生成双评的考生数量小于剩余应生成的数量)
+                            needDouble = (doubleMarkTaskCount2 + doubleCount) < expectCount
+                                    && ((studentCount - libraryCount + doubleCount) <= (expectCount
+                                    - doubleMarkTaskCount2 - doubleCount));
+                        }
+                        if (needDouble) {
+                            MarkTask markTask = new MarkTask();
+                            markTask.setId(SystemConstant.getDbUuid());
+                            markTask.setExamId(student.getExamId());
+                            markTask.setCourseId(student.getCourseId());
+                            markTask.setPaperNumber(student.getPaperNumber());
+                            markTask.setQuestionId(markQuestion.getId());
+                            markTask.setMainNumber(markQuestion.getMainNumber());
+                            markTask.setSubNumber(markQuestion.getSubNumber());
+                            markTask.setStudentId(student.getId());
+                            markTask.setBasicStudentId(student.getBasicStudentId());
+                            markTask.setStudentCode(student.getStudentCode());
+                            markTask.setSecretNumber(student.getSecretNumber());
+                            markTask.setTaskNumber(2);
+                            markTask.setStatus(MarkTaskStatus.WAITING);
+                            taskList.add(markTask);
+                            doubleCount++;
+                        }
+                    }
+                }
+                // 有新任务创建
+                if (CollectionUtils.isNotEmpty(taskList)) {
+                    count += taskList.size();
+                    // 任务乱序
+                    Collections.shuffle(taskList);
+                    // 批量保存
+                    markTaskService.saveBatch(taskList);
+                    // 正评状态才需要更新任务数量
+                    if (MarkPaperStatus.FORMAL.equals(markPaper.getStatus())) {
+                        this.updateMarkTaskCount(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+                        this.updateMarkedCount(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId());
+                    }
+                }
+                studentList = markStudentService.listUnMarkTaskStudent(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getId(), pageSize);
+            }
+            if (count != 0) {
+                log.info("finish create " + count + " markTask for examId=" + markQuestion.getExamId() + ", paperNumber="
+                        + markQuestion.getPaperNumber() + ", questionId=" + markQuestion.getId());
+            }
+        } catch (Exception e) {
+            log.error("build formal markTask error for examId=" + markQuestion.getExamId() + ", paperNumber="
+                    + markQuestion.getPaperNumber() + ", questionId=" + markQuestion.getId(), e);
+        } finally {
+            lockService.unwatch(LockType.QUESTION, markQuestion.getId());
+        }
+    }
+
     @Transactional
     @Override
     public void deleteMarkTaskByStudent(MarkStudent student) {
@@ -839,7 +927,7 @@ public class MarkServiceImpl implements MarkService {
         page.addOrder(orderItem);
         IPage<Long> list = markTaskService.listPageHistory(page, userId, examId, paperNumber, secretNumber, markerScore);
         List<Task> recordsDtos = new ArrayList<>();
-        List<MarkTaskStatus> markTaskStatuses = Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.WAIT_ARBITRATE,MarkTaskStatus.PROBLEM);
+        List<MarkTaskStatus> markTaskStatuses = Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.WAIT_ARBITRATE, MarkTaskStatus.PROBLEM);
         for (Long studentId : list.getRecords()) {
             List<MarkTask> markTaskList = markTaskService.listByStudentIdAndMarkerId(studentId, userId, markTaskStatuses);
             Task dto = taskService.build(userId, markTaskList);

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

@@ -895,6 +895,14 @@ public class MarkStudentServiceImpl extends ServiceImpl<MarkStudentMapper, MarkS
         return markStudentIPage.getRecords();
     }
 
+    @Override
+    public List<MarkStudent> listUnMarkDoubleTaskStudent(Long examId, String paperNumber, Long questionId, int pageSize) {
+        Page<MarkStudent> page = new Page<>(1, pageSize);
+        IPage<MarkStudent> markStudentIPage = this.baseMapper.listUnMarkDoubleTaskStudent(page, examId, paperNumber,
+                questionId);
+        return markStudentIPage.getRecords();
+    }
+
     /**
      * 客观题统分 统分场景: 1.后台-成绩检查-客观题检查 2.扫描端-考生图片上传 3.扫描端-客观题二次识别
      */

+ 19 - 0
teachcloud-mark/src/main/resources/mapper/MarkArchiveStudentMapper.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qmth.teachcloud.mark.mapper.MarkArchiveStudentMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.qmth.teachcloud.mark.entity.MarkArchiveStudent">
+        <id column="student_id" property="studentId" />
+        <result column="exam_id" property="examId" />
+        <result column="paper_number" property="paperNumber" />
+        <result column="student_code" property="studentCode" />
+        <result column="basic_student_id" property="basicStudentId" />
+        <result column="sheet_urls" property="sheetUrls" />
+        <result column="subjective_questions" property="subjectiveQuestions" />
+        <result column="objective_questions" property="objectiveQuestions" />
+        <result column="card_content" property="cardContent" />
+        <result column="create_time" property="createTime" />
+    </resultMap>
+
+</mapper>

+ 15 - 0
teachcloud-mark/src/main/resources/mapper/MarkStudentMapper.xml

@@ -269,6 +269,21 @@
                          WHERE ms.id = mt.student_id
                            AND mt.question_id = #{questionId})
     </select>
+    <select id="listUnMarkDoubleTaskStudent" resultMap="BaseResultMap">
+        SELECT ms.*
+        FROM mark_student ms
+        WHERE ms.exam_id = #{examId}
+          AND ms.paper_number = #{paperNumber}
+          AND ms.is_upload = TRUE
+          AND is_absent = FALSE
+          AND is_manual_absent = FALSE
+          AND omr_absent = FALSE
+          AND NOT EXISTS(SELECT 1
+                         FROM mark_task mt
+                         WHERE ms.id = mt.student_id
+                           AND mt.question_id = #{questionId}
+                            AND mt.task_number = 2)
+    </select>
     <sql id="queryWhereAndOrder">
         where t.exam_id=#{query.examId}
         <if test="query.studentId != null and query.studentId !=''">