Explorar o código

3.4.4 update-20250402,bug修改

xiaofei hai 2 meses
pai
achega
d363baab96
Modificáronse 20 ficheiros con 961 adicións e 241 borrados
  1. 14 14
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/dto/ObjectiveStructDto.java
  2. 134 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/dto/ObjectiveStructImportDto.java
  3. 10 2
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/PrintFinishServiceImpl.java
  4. 86 6
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/execute/AsyncObjectiveStructImportService.java
  5. 4 5
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/execute/AsyncSubjectiveStructImportService.java
  6. 220 167
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/service/impl/ImportLogicServiceImpl.java
  7. 404 0
      distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_2.java
  8. 2 4
      distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_4.java
  9. 2 2
      distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_5.java
  10. 14 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/enums/OptionsEnum.java
  11. 2 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/mapper/MarkQuestionMapper.java
  12. 4 2
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkPaperService.java
  13. 2 2
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkQuestionService.java
  14. 0 2
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkService.java
  15. 1 1
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkUserQuestionService.java
  16. 27 10
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkPaperServiceImpl.java
  17. 9 14
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkQuestionServiceImpl.java
  18. 0 8
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkServiceImpl.java
  19. 9 2
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkUserQuestionServiceImpl.java
  20. 17 0
      teachcloud-mark/src/main/resources/mapper/MarkQuestionMapper.xml

+ 14 - 14
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/dto/ObjectiveStructDto.java

@@ -1,71 +1,63 @@
 package com.qmth.distributed.print.business.bean.dto;
 
 import com.qmth.boot.tools.excel.annotation.ExcelColumn;
-import com.qmth.teachcloud.common.annotation.ExcelError;
-import com.qmth.teachcloud.common.annotation.ExcelProperty;
 import io.swagger.annotations.ApiModelProperty;
 
+import java.io.Serializable;
+
 /**
  * @Description: 客观题结构Dto
  * @Author: CaoZixuan
  * @Date: 2024/04/08
  */
-public class ObjectiveStructDto {
+public class ObjectiveStructDto implements Serializable {
 
     @ApiModelProperty(value = "课程代码")
     @ExcelColumn(name = "课程代码", index = 1, nullable = true)
-    @ExcelProperty(name = "课程代码", width = 20, index = 1)
     private String courseCode;
 
     @ApiModelProperty(value = "课程名称")
     @ExcelColumn(name = "课程名称", index = 2, nullable = true)
-    @ExcelProperty(name = "课程名称", width = 20, index = 2)
     private String courseName;
 
     @ApiModelProperty(value = "试卷编号")
     @ExcelColumn(name = "试卷编号", index = 3, nullable = true)
-    @ExcelProperty(name = "试卷编号", width = 20, index = 3)
     private String paperNumber;
 
     @ApiModelProperty(value = "大题名称")
     @ExcelColumn(name = "大题名称", index = 4, nullable = true)
-    @ExcelProperty(name = "大题名称", width = 20, index = 4)
     private String mainTitle;
 
     @ApiModelProperty(value = "大题号(只能用小写数字)")
     @ExcelColumn(name = "大题号(只能用小写数字)", index = 5, nullable = true)
-    @ExcelProperty(name = "大题号(只能用小写数字)", width = 20, index = 5)
     private Integer mainNumber;
 
     @ApiModelProperty(value = "小题号(只能用小写数字)")
     @ExcelColumn(name = "小题号(只能用小写数字)", index = 6, nullable = true)
-    @ExcelProperty(name = "小题号(只能用小写数字)", width = 20, index = 6)
     private Integer subNumber;
 
     @ApiModelProperty(value = "标准答案")
     @ExcelColumn(name = "标准答案", index = 7, nullable = true)
-    @ExcelProperty(name = "标准答案", width = 20, index = 7)
     private String answer;
 
     @ApiModelProperty(value = "选项个数")
     @ExcelColumn(name = "选项个数", index = 8, nullable = true)
-    @ExcelProperty(name = "选项个数", width = 20, index = 8)
     private Integer optionCount;
 
     @ApiModelProperty(value = "小题满分")
     @ExcelColumn(name = "小题满分", index = 9, nullable = true)
-    @ExcelProperty(name = "小题满分", width = 20, index = 9)
     private Double totalScore;
 
     @ApiModelProperty(value = "题型(1-单选,2-多选,3-判断)")
     @ExcelColumn(name = "题型(1-单选,2-多选,3-判断)", index = 10, nullable = true)
-    @ExcelProperty(name = "题型(1-单选,2-多选,3-判断)", width = 20, index = 10)
     private Integer questionType;
 
     @ApiModelProperty(value = "错误信息")
-    @ExcelError
+    @ExcelColumn(name = "错误信息", index = 11, nullable = true)
     private String errorMsg;
 
+    private Long courseId;
+
     public String getCourseCode() {
         return courseCode;
     }
@@ -153,4 +145,12 @@ public class ObjectiveStructDto {
     public void setErrorMsg(String errorMsg) {
         this.errorMsg = errorMsg;
     }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
 }

+ 134 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/dto/ObjectiveStructImportDto.java

@@ -0,0 +1,134 @@
+package com.qmth.distributed.print.business.bean.dto;
+
+import com.qmth.boot.tools.excel.annotation.ExcelColumn;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * @Description: 客观题结构Dto
+ * @Author: CaoZixuan
+ * @Date: 2024/04/08
+ */
+public class ObjectiveStructImportDto implements Serializable {
+
+    @ApiModelProperty(value = "课程代码")
+    @ExcelColumn(name = "课程代码", index = 1, nullable = true)
+    private String courseCode;
+
+    @ApiModelProperty(value = "课程名称")
+    @ExcelColumn(name = "课程名称", index = 2, nullable = true)
+    private String courseName;
+
+    @ApiModelProperty(value = "试卷编号")
+    @ExcelColumn(name = "试卷编号", index = 3, nullable = true)
+    private String paperNumber;
+
+    @ApiModelProperty(value = "大题名称")
+    @ExcelColumn(name = "大题名称", index = 4, nullable = true)
+    private String mainTitle;
+
+    @ApiModelProperty(value = "大题号(只能用小写数字)")
+    @ExcelColumn(name = "大题号(只能用小写数字)", index = 5, nullable = true)
+    private Integer mainNumber;
+
+    @ApiModelProperty(value = "小题号(只能用小写数字)")
+    @ExcelColumn(name = "小题号(只能用小写数字)", index = 6, nullable = true)
+    private Integer subNumber;
+
+    @ApiModelProperty(value = "标准答案")
+    @ExcelColumn(name = "标准答案", index = 7, nullable = true)
+    private String answer;
+
+    @ApiModelProperty(value = "选项个数")
+    @ExcelColumn(name = "选项个数", index = 8, nullable = true)
+    private Integer optionCount;
+
+    @ApiModelProperty(value = "小题满分")
+    @ExcelColumn(name = "小题满分", index = 9, nullable = true)
+    private Double totalScore;
+
+    @ApiModelProperty(value = "题型(1-单选,2-多选,3-判断)")
+    @ExcelColumn(name = "题型(1-单选,2-多选,3-判断)", index = 10, nullable = true)
+    private Integer questionType;
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getPaperNumber() {
+        return paperNumber;
+    }
+
+    public void setPaperNumber(String paperNumber) {
+        this.paperNumber = paperNumber;
+    }
+
+    public String getMainTitle() {
+        return mainTitle;
+    }
+
+    public void setMainTitle(String mainTitle) {
+        this.mainTitle = mainTitle;
+    }
+
+    public Integer getMainNumber() {
+        return mainNumber;
+    }
+
+    public void setMainNumber(Integer mainNumber) {
+        this.mainNumber = mainNumber;
+    }
+
+    public Integer getSubNumber() {
+        return subNumber;
+    }
+
+    public void setSubNumber(Integer subNumber) {
+        this.subNumber = subNumber;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+
+    public Integer getOptionCount() {
+        return optionCount;
+    }
+
+    public void setOptionCount(Integer optionCount) {
+        this.optionCount = optionCount;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Integer getQuestionType() {
+        return questionType;
+    }
+
+    public void setQuestionType(Integer questionType) {
+        this.questionType = questionType;
+    }
+}

+ 10 - 2
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/PrintFinishServiceImpl.java

@@ -312,7 +312,11 @@ public class PrintFinishServiceImpl implements PrintFinishService {
                             MarkQuestionAnswer markQuestionAnswer = new MarkQuestionAnswer(examId, paperNumber, paperType, struct.getMainNumber(), struct.getSubNumber());
                             if (!map.isEmpty() && map.get(paperType) != null) {
                                 // 客观题有标答时,给分策略默认为全对给分
-                                markQuestionAnswer.setAnswer(map.get(paperType).getByMainNumberAndSubNumber(markQuestion.getMainNumber(), markQuestion.getSubNumber()).getAnswerString());
+                                String answer = struct.getAnswer();
+                                if (StringUtils.isBlank(answer)) {
+                                    answer = map.get(paperType).getByMainNumberAndSubNumber(markQuestion.getMainNumber(), markQuestion.getSubNumber()).getAnswerString();
+                                }
+                                markQuestionAnswer.setAnswer(answer);
                                 markQuestionAnswer.setObjectivePolicy(ObjectivePolicy.NONE);
                             }
                             markQuestionAnswers.add(markQuestionAnswer);
@@ -329,12 +333,16 @@ public class PrintFinishServiceImpl implements PrintFinishService {
                         // 解析评卷区坐标
                         markQuestionService.updatePic(content, questions);
                         // 命题老师,任课老师,默认为评卷员
-                        markUserQuestionService.saveDefaultMarker(examId, paperNumber, questions);
+                        markUserQuestionService.saveDefaultMarker(examId, paperNumber);
                     }
                 } catch (Exception e) {
                     log.info("解析评卷区失败");
                 }
             }
+
+            // 更新证卷设置中的状态
+            markPaperService.updateQuestionStatus(examId, paperNumber);
+            markPaperService.updateGroupStatus(examId, paperNumber);
         } catch (Exception e) {
             log.info("同步结构失败");
         }

+ 86 - 6
distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/execute/AsyncObjectiveStructImportService.java

@@ -1,26 +1,46 @@
 package com.qmth.distributed.print.business.templete.execute;
 
 import cn.hutool.core.date.DateUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.qmth.boot.tools.excel.ExcelWriter;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.distributed.print.business.bean.dto.ObjectiveStructDto;
+import com.qmth.distributed.print.business.bean.dto.SubjectiveStructDto;
 import com.qmth.distributed.print.business.templete.importData.AsyncImportTaskTemplete;
 import com.qmth.distributed.print.business.templete.importData.SyncImportTaskTemplate;
 import com.qmth.distributed.print.business.templete.service.ImportLogicService;
 import com.qmth.distributed.print.business.templete.service.TaskLogicService;
+import com.qmth.teachcloud.common.bean.vo.FilePathVo;
 import com.qmth.teachcloud.common.contant.SpringContextHolder;
 import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.entity.MarkQuestion;
 import com.qmth.teachcloud.common.entity.TBTask;
 import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
 import com.qmth.teachcloud.common.enums.TaskResultEnum;
 import com.qmth.teachcloud.common.enums.TaskStatusEnum;
+import com.qmth.teachcloud.common.enums.UploadFileEnum;
+import com.qmth.teachcloud.common.service.FileUploadService;
 import com.qmth.teachcloud.common.service.TBTaskService;
 import com.qmth.teachcloud.common.util.Result;
 import com.qmth.teachcloud.common.util.ResultUtil;
+import com.qmth.teachcloud.mark.entity.MarkPaper;
+import com.qmth.teachcloud.mark.entity.MarkQuestionAnswer;
+import com.qmth.teachcloud.mark.entity.MarkUserQuestion;
+import com.qmth.teachcloud.mark.service.MarkPaperService;
+import com.qmth.teachcloud.mark.service.MarkQuestionAnswerService;
+import com.qmth.teachcloud.mark.service.MarkQuestionService;
+import com.qmth.teachcloud.mark.service.MarkUserQuestionService;
+import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.Resource;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.text.MessageFormat;
-import java.util.Date;
-import java.util.Map;
-import java.util.StringJoiner;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @Description: 试卷客观题结构导入同步任务
@@ -32,6 +52,15 @@ public class AsyncObjectiveStructImportService extends AsyncImportTaskTemplete {
 
     public static final String OBJ_TITLE = "客观题结构导入";
 
+    @Resource
+    private MarkQuestionService markQuestionService;
+    @Resource
+    private MarkQuestionAnswerService markQuestionAnswerService;
+    @Resource
+    private MarkPaperService markPaperService;
+    @Resource
+    private FileUploadService fileUploadService;
+
     @Override
     public Result importTask(Map<String, Object> map) throws Exception {
         TBTask tbTask = (TBTask) map.get(SystemConstant.TASK);
@@ -45,9 +74,47 @@ public class AsyncObjectiveStructImportService extends AsyncImportTaskTemplete {
             map.put("inputStream", inputStream);
             ImportLogicService importLogicService = SpringContextHolder.getBean(ImportLogicService.class);
             // 执行导入考务数据
-            importLogicService.executeImportObjectiveStructLogic(map);
-            tbTask.setResult(TaskResultEnum.SUCCESS);
-            stringJoinerSummary.add("导入客观题结构成功");
+            Map<String, Object> result = importLogicService.executeImportObjectiveStructLogic(map);
+            boolean hasError = Boolean.parseBoolean(String.valueOf(result.get(SystemConstant.HAS_ERROR_DATA)));
+
+            if (hasError) {
+                SystemConstant.addSummary(stringJoinerSummary, "有异常数据,开始生成错误文件");
+                tbTask.setResult(TaskResultEnum.ERROR);
+                // 有异常数据
+                List<ObjectiveStructDto> errorDataList = JSON.parseArray(JSON.toJSONString(result.get(SystemConstant.ERROR_DATA_LIST)), ObjectiveStructDto.class);
+                try {
+                    File excelFileTemp = SystemConstant.getFileTempVar(SystemConstant.EXCEL_PREFIX);
+                    this.createErrorExcelFile(errorDataList, excelFileTemp);
+                    String fileName = SystemConstant.getNanoId() + SystemConstant.EXCEL_PREFIX;
+                    FilePathVo filePathVo = fileUploadService.uploadFile(excelFileTemp, UploadFileEnum.FILE, fileName);
+                    tbTask.setErrorFilePath(JSON.toJSONString(filePathVo));
+                    SystemConstant.addSummary(stringJoinerSummary, "错误文件生成生成,请到\"导入结果查询\"中,下载错误文件");
+                } catch (Exception e) {
+                    SystemConstant.addSummary(stringJoinerSummary, "错误文件生成失败," + e.getMessage());
+                }
+            } else {
+                SystemConstant.addSummary(stringJoinerSummary, "数据校验通过,开始写入试卷结构数据库");
+                List<MarkQuestion> markQuestions = JSON.parseArray(JSON.toJSONString(result.get(SystemConstant.DATASOURCE)), MarkQuestion.class);
+                markQuestionService.saveOrUpdateBatch(markQuestions);
+                SystemConstant.addSummary(stringJoinerSummary, "写入试卷结构数据库完成,开始写入客观题标答数据库");
+
+                // 更新扫描阅卷数据
+                List<MarkQuestionAnswer> markQuestionAnswers = JSON.parseArray(JSON.toJSONString(result.get(SystemConstant.DATASOURCE1)), MarkQuestionAnswer.class);
+                markQuestionAnswerService.saveOrUpdateBatch(markQuestionAnswers);
+                // 更改试卷结构状态为已提交
+                Set<String> paperNumberSet = markQuestions.stream().map(MarkQuestion::getPaperNumber).collect(Collectors.toSet());
+                if (CollectionUtils.isNotEmpty(paperNumberSet)) {
+                    for (String paperNumber : paperNumberSet) {
+                        markPaperService.updateQuestionStatus(tbTask.getExamId(), paperNumber);
+                    }
+                }
+                SystemConstant.addSummary(stringJoinerSummary, "写入客观题标答数据库完成,导入结束");
+                tbTask.setResult(TaskResultEnum.SUCCESS);
+
+                stringJoinerSummary.add(MessageFormat.format("{0}{1}{2}{3}{4}{5}{6}{7}",
+                        DateUtil.format(new Date(), SystemConstant.DEFAULT_DATE_PATTERN), FINISH_TITLE, markQuestions.size(),
+                        FINISH_TOTAL_SIZE, markQuestions.size(), FINISH_SUCCESS_SIZE, 0, FINISH_ERROR_SIZE));
+            }
         } catch (Exception e) {
             // 导入异常错误信息写入summary
             stringJoinerSummary.add("导入客观题结构失败," + EXCEPTION_DATA + System.lineSeparator() + e.getMessage());
@@ -60,4 +127,17 @@ public class AsyncObjectiveStructImportService extends AsyncImportTaskTemplete {
         }
         return ResultUtil.ok();
     }
+
+    private void createErrorExcelFile(List<ObjectiveStructDto> list, File excelFileTemp) {
+        try {
+            FileOutputStream outputStream = new FileOutputStream(excelFileTemp);
+            ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
+            writer.writeObjects("客观题导入数据", null, ObjectiveStructDto.class, list.listIterator());
+            writer.output(outputStream);
+            outputStream.flush();
+            outputStream.close();
+        } catch (Exception e) {
+
+        }
+    }
 }

+ 4 - 5
distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/execute/AsyncSubjectiveStructImportService.java

@@ -104,11 +104,10 @@ public class AsyncSubjectiveStructImportService extends AsyncImportTaskTemplete
                 // 更改试卷结构状态为已提交
                 Set<String> paperNumberSet = markQuestions.stream().map(MarkQuestion::getPaperNumber).collect(Collectors.toSet());
                 if (CollectionUtils.isNotEmpty(paperNumberSet)) {
-                    markPaperService.update(new UpdateWrapper<MarkPaper>().lambda()
-                            .set(MarkPaper::getQuestionStatus, true)
-                            .set(MarkPaper::getGroupStatus, true)
-                            .eq(MarkPaper::getExamId, tbTask.getExamId())
-                            .in(MarkPaper::getPaperNumber, paperNumberSet));
+                    for (String paperNumber : paperNumberSet) {
+                        markPaperService.updateQuestionStatus(tbTask.getExamId(), paperNumber);
+                        markPaperService.updateGroupStatus(tbTask.getExamId(), paperNumber);
+                    }
                 }
                 SystemConstant.addSummary(stringJoinerSummary, "写入用户-结构数据库完成,导入结束");
                 tbTask.setResult(TaskResultEnum.SUCCESS);

+ 220 - 167
distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/service/impl/ImportLogicServiceImpl.java

@@ -2,11 +2,11 @@ package com.qmth.distributed.print.business.templete.service.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.qmth.boot.tools.excel.ExcelReader;
 import com.qmth.boot.tools.excel.enums.ExcelType;
 import com.qmth.boot.tools.excel.model.DataMap;
 import com.qmth.distributed.print.business.bean.dto.ObjectiveStructDto;
+import com.qmth.distributed.print.business.bean.dto.ObjectiveStructImportDto;
 import com.qmth.distributed.print.business.bean.dto.SubjectiveStructDto;
 import com.qmth.distributed.print.business.bean.dto.SubjectiveStructImportDto;
 import com.qmth.distributed.print.business.bean.dto.importFile.BasicExamStudentImport;
@@ -19,19 +19,16 @@ import com.qmth.distributed.print.business.enums.PrintPlanStatusEnum;
 import com.qmth.distributed.print.business.enums.RequiredFieldsEnum;
 import com.qmth.distributed.print.business.service.*;
 import com.qmth.distributed.print.business.templete.service.ImportLogicService;
-import com.qmth.teachcloud.common.bean.dto.mark.MarkUser;
 import com.qmth.teachcloud.common.bean.examRule.CodeNameEnableDisabledValue;
 import com.qmth.teachcloud.common.bean.examRule.CodeNameEnableValue;
-import com.qmth.teachcloud.common.bean.params.UserSaveParams;
-import com.qmth.teachcloud.common.bean.result.ExcelResult;
 import com.qmth.teachcloud.common.bean.result.SysUserResult;
 import com.qmth.teachcloud.common.contant.SystemConstant;
 import com.qmth.teachcloud.common.entity.*;
 import com.qmth.teachcloud.common.enums.*;
 import com.qmth.teachcloud.common.service.*;
 import com.qmth.teachcloud.common.util.ConvertUtil;
-import com.qmth.teachcloud.mark.bean.answerbatch.Paper;
 import com.qmth.teachcloud.mark.entity.MarkPaper;
+import com.qmth.teachcloud.mark.entity.MarkQuestionAnswer;
 import com.qmth.teachcloud.mark.entity.MarkUserQuestion;
 import com.qmth.teachcloud.mark.service.MarkPaperService;
 import com.qmth.teachcloud.mark.service.MarkQuestionAnswerService;
@@ -902,190 +899,210 @@ public class ImportLogicServiceImpl implements ImportLogicService {
             throw ExceptionResultEnum.ERROR.exception("考试不存在");
         }
         if (!ExamModelEnum.MODEL4.equals(basicExam.getExamModel())) {
-            throw ExceptionResultEnum.ERROR.exception("仅针对模式4的考试可以导入");
+            throw ExceptionResultEnum.ERROR.exception("非模式4的考试,无法使用此功能");
         }
-        // 课程编号名称校验
-        Map<String, Set<String>> courseCheckMap = basicCourseService.list(
-                new QueryWrapper<BasicCourse>().lambda().eq(BasicCourse::getSchoolId, schoolId)).stream().collect(
-                Collectors.toMap(BasicCourse::getCode, v -> new HashSet<>(Collections.singletonList(v.getName())), (Set<String> v1, Set<String> v2) -> {
-                    v1.addAll(v2);
-                    return v1;
-                }));
 
         List<MarkPaper> markPaperList = markPaperService.findMarkPaperListByExamId(examId);
-        // 题号重复校验
-        // 导入的客观题题号集合
-        Set<String> questionNumberCheckSet = new HashSet<>();
-        // 客观题选项个数在大题下一致性校验
-        Map<String, Integer> questionOptionCountMap = new HashMap<>();
-        // 该试卷已存在的主观题题号集合
-        Set<String> dbSubjectiveQuestionNumberSet = markQuestionService.list(
-                        new QueryWrapper<MarkQuestion>().lambda().eq(MarkQuestion::getExamId, examId)
-                                .eq(MarkQuestion::getObjective, false)).stream()
-                .map(e -> e.getPaperNumber() + SystemConstant.HYPHEN + e.getMainNumber() + SystemConstant.HYPHEN + e.getSubNumber()).collect(Collectors.toSet());
-
-        ExcelResult<ObjectiveStructDto> excelResult = ConvertUtil.analyzeExcel(inputStream, ObjectiveStructDto.class,
-                true, 0);
-        List<ObjectiveStructDto> objectiveStructDtoList = excelResult.getDatasource();
-        List<MarkQuestion> markQuestionList = new ArrayList<>();
-        for (ObjectiveStructDto objectiveStructDto : objectiveStructDtoList) {
-            List<String> logicErrorList = new ArrayList<>();
-            // 公共异常提示
-            String logicErrorCommonNotice = "";
-
-            String courseCode = objectiveStructDto.getCourseCode();
-            String courseName = objectiveStructDto.getCourseName();
-            String paperNumber = objectiveStructDto.getPaperNumber();
-            String mainTitle = objectiveStructDto.getMainTitle();
-            Integer mainNumber = objectiveStructDto.getMainNumber();
-            Integer subNumber = objectiveStructDto.getSubNumber();
-            String answer = objectiveStructDto.getAnswer();
-            Integer optionCount = objectiveStructDto.getOptionCount();
-            Double totalScore = objectiveStructDto.getTotalScore();
-            Integer questionType = objectiveStructDto.getQuestionType();
-            String errorMsg = objectiveStructDto.getErrorMsg();
-
-            // 1.题号重复校验
-            if (SystemConstant.strNotNull(paperNumber) && mainNumber != null && subNumber != null) {
-                logicErrorCommonNotice = String.format("课程代码为[%s],试卷编号为[%s],大题号为[%s],小题号为[%s]的数据异常 : ", courseCode,
-                        paperNumber, mainNumber, subNumber);
-                String questionNumberKey =
-                        courseCode + SystemConstant.HYPHEN + paperNumber + SystemConstant.HYPHEN + mainNumber + SystemConstant.HYPHEN + subNumber;
-                if (questionNumberCheckSet.contains(questionNumberKey)) {
-                    logicErrorList.add("excel中题号重复");
-                } else {
-                    questionNumberCheckSet.add(questionNumberKey);
+        Map<String, MarkPaper> markPaperCourseMap = markPaperList.stream().collect(Collectors.toMap(MarkPaper::getCourseCode, Function.identity()));
+        Map<String, MarkPaper> markPaperPaperNumberMap = markPaperList.stream().collect(Collectors.toMap(MarkPaper::getPaperNumber, Function.identity()));
+
+        List<MarkQuestion> markQuestionList = markQuestionService.listByExamIdAndObjective(examId, true);
+        Set<String> objectivePaperNumberSet = markQuestionList.stream().map(MarkQuestion::getPaperNumber).collect(Collectors.toSet());
+
+        List<MarkQuestion> markQuestionList1 = markQuestionService.listByExamIdAndObjective(examId, false);
+        Set<String> subjectivePaperNumberSet = markQuestionList1.stream().map(m -> SystemConstant.mergeString(SystemConstant.HYPHEN, m.getPaperNumber(), m.getMainNumber(), m.getSubNumber())).collect(Collectors.toSet());
+
+        List<SysUserResult> sysUserList = sysUserService.listBySchoolId(schoolId);
+        Map<String, SysUserResult> sysUserMap = sysUserList.stream().collect(Collectors.toMap(SysUserResult::getLoginName, Function.identity()));
+
+        ExcelReader excelReader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
+        List<ObjectiveStructImportDto> objectiveStructImportDtoList = excelReader.getObjectList(ObjectiveStructImportDto.class);
+
+        // 课程下试卷编号map
+        Map<String, String> coursePaperNumberMap = new HashMap<>();
+        // 课程、试卷编号下大题号、小题号
+        Map<String, String> coursePaperQuestionMap = new HashMap<>();
+
+        List<MarkQuestion> markQuestions = new ArrayList<>();
+        List<MarkQuestionAnswer> markQuestionAnswers = new ArrayList<>();
+        boolean hasError = false;
+        List<ObjectiveStructDto> objectiveStructDtos = new ArrayList<>();
+        // 按课程+试卷编号分组
+        for (ObjectiveStructImportDto objectiveStructImportDto : objectiveStructImportDtoList) {
+            ObjectiveStructDto objectiveStructDto = new ObjectiveStructDto();
+            BeanUtils.copyProperties(objectiveStructImportDto, objectiveStructDto);
+            try {
+                List<String> errorList = new ArrayList<>();
+
+                if (StringUtils.isBlank(objectiveStructDto.getCourseCode())) {
+                    errorList.add("课程代码不能为空");
+                }
+                if (StringUtils.isBlank(objectiveStructDto.getCourseName())) {
+                    errorList.add("课程名称不能为空");
                 }
-                if (dbSubjectiveQuestionNumberSet.contains(questionNumberKey)) {
-                    logicErrorList.add("该题号已存在主观题中");
+                if (StringUtils.isBlank(objectiveStructDto.getPaperNumber())) {
+                    errorList.add("试卷编号不能为空");
                 }
-                String mainQuestionKey = paperNumber + SystemConstant.HYPHEN + mainNumber;
-                if (questionOptionCountMap.containsKey(mainQuestionKey)) {
-                    if (!Objects.equals(questionOptionCountMap.get(mainQuestionKey), optionCount)) {
-                        logicErrorList.add("选项个数和该大题中的其他小题不一致");
-                    }
-                } else {
-                    questionOptionCountMap.put(mainQuestionKey, optionCount);
+                String mainTitle = objectiveStructDto.getMainTitle();
+                if (StringUtils.isBlank(mainTitle)) {
+                    errorList.add("大题名称不能为空");
+                }
+                Integer mainNumber = objectiveStructDto.getMainNumber();
+                if (mainNumber == null) {
+                    errorList.add("大题号不能为空");
+                }
+                Integer subNumber = objectiveStructDto.getSubNumber();
+                if (subNumber == null) {
+                    errorList.add("小题号不能为空");
+                }
+                String answer = objectiveStructDto.getAnswer();
+                if (StringUtils.isBlank(answer)) {
+                    errorList.add("标答不能为空");
+                }
+                Integer optionCount = objectiveStructDto.getOptionCount();
+                if (optionCount == null) {
+                    errorList.add("选项个数不能为空");
+                }
+                Double totalScore = objectiveStructDto.getTotalScore();
+                if (totalScore == null) {
+                    errorList.add("小题满分不能为空");
+                }
+                Integer questionType = objectiveStructDto.getQuestionType();
+                if (questionType == null) {
+                    errorList.add("题型不能为空");
+                }
+                if (CollectionUtils.isNotEmpty(errorList)) {
+                    throw ExceptionResultEnum.ERROR.exception(String.join(";", errorList));
                 }
-            }
 
-            // 2.校验课程编号,课程名称,试卷在考试下是否存在
-            if (SystemConstant.strNotNull(courseCode) && SystemConstant.strNotNull(courseName) && SystemConstant.strNotNull(paperNumber)) {
-                if (!courseCheckMap.containsKey(courseCode)) {
-                    logicErrorList.add("课程编号不存在");
-                } else if (!courseCheckMap.get(courseCode).contains(courseName)) {
-                    logicErrorList.add("课程编号名称不匹配");
+                // 检查选项个数和标答范围是否一对待
+                validObjectiveAnswer(answer, optionCount, questionType);
+
+                String courseCode = objectiveStructDto.getCourseCode().trim();
+                String courseName = objectiveStructDto.getCourseName().trim();
+                String paperNumber = objectiveStructDto.getPaperNumber().trim();
+
+                // 课程是否存在
+                if (!markPaperCourseMap.containsKey(courseCode)) {
+                    throw ExceptionResultEnum.ERROR.exception("未查询到此课程");
+                } else {
+                    MarkPaper markPaper = markPaperCourseMap.get(courseCode);
+                    if (!courseName.equals(markPaper.getCourseName())) {
+                        throw ExceptionResultEnum.ERROR.exception("评卷设置中,课程名称为[" + markPaper.getCourseName() + "]");
+                    }
+                    objectiveStructDto.setCourseId(markPaper.getCourseId());
                 }
-                if (markPaperList.stream().noneMatch(
-                        m -> m.getPaperNumber().equals(paperNumber) && m.getCourseCode().equals(courseCode) && m.getCourseName().equals(courseName))) {
-                    logicErrorList.add("考试课程中不存在该试卷,请先创建试卷");
+
+                // 试卷编号是否存在
+                if (!markPaperPaperNumberMap.containsKey(paperNumber)) {
+                    throw ExceptionResultEnum.ERROR.exception("未查询到此试卷编号");
                 }
-            }
-            // 3.选项越界校验
-            List<String> optionScope = new ArrayList<>();
-            try {
-                for (int j = 1; j <= optionCount; j++) {
-                    String code = OptionsEnum.getCodeByIndex(j);
-                    optionScope.add(code);
+
+                // 试卷编号是否设置了主观题
+                if (objectivePaperNumberSet.contains(paperNumber)) {
+                    throw ExceptionResultEnum.ERROR.exception("该试卷编号下已经存在客观题试卷结构,不允许导入");
                 }
-            } catch (Exception e) {
-                throw ExceptionResultEnum.ERROR.exception(e.getMessage());
-            }
 
-            // 答案选项越界校验
-            List<String> answerList = Arrays.asList(answer.split(""));
-            if (CollectionUtils.isNotEmpty(answerList)) {
-                for (String cell : answerList) {
-                    String outOfBoundsException = String.format("答案[%s]错误。答案只能从[%s]中选择", cell, String.join("", optionScope));
-                    try {
-                        OptionsEnum optionsEnum = OptionsEnum.getByCode(cell);
-                        int optionIndex = optionsEnum.getIndex();
-                        if (optionIndex > optionCount) {
-                            // 答案选项超出范围
-                            logicErrorList.add(outOfBoundsException);
-                        }
-                    } catch (Exception e) {
-                        logicErrorList.add(outOfBoundsException);
+                // 校验课程下试卷编号唯一
+                if (coursePaperNumberMap.containsKey(courseCode)) {
+                    if (!paperNumber.equals(coursePaperNumberMap.get(courseCode))) {
+                        errorList.add("课程存在不同试卷编号");
+                        throw ExceptionResultEnum.ERROR.exception(String.join(";", errorList));
                     }
+                } else {
+                    coursePaperNumberMap.put(courseCode, paperNumber);
                 }
-            } else {
-                logicErrorList.add("答案必填");
-            }
 
-            // 4.判断答案是否符合题型
-            if (questionType != null && SystemConstant.strNotNull(answer)) {
-                switch (questionType) {
-                    case 1:
-                        if (answer.length() > 1) {
-                            logicErrorList.add("单选题答案只能有一个");
-                        }
-                        break;
-                    case 2:
-                        break;
-                    case 3:
-                        if (optionCount != 2) {
-                            logicErrorList.add("判断题[选项个数]只能为2");
+                // 校验试卷编号唯一
+                if (coursePaperNumberMap.containsValue(paperNumber)) {
+                    for (Map.Entry<String, String> entry : coursePaperNumberMap.entrySet()) {
+                        if (entry.getValue().equals(paperNumber) && !courseCode.equals(entry.getKey())) {
+                            errorList.add("试卷编号已被其它课程占用");
+                            throw ExceptionResultEnum.ERROR.exception(String.join(";", errorList));
                         }
-                        break;
-                    default:
-                        logicErrorList.add("[题型(1-单选,2-多选,3-判断)]必须从1、2、3中填写");
+                    }
                 }
-            }
 
-            // 整理异常信息
-            if (CollectionUtils.isNotEmpty(logicErrorList)) {
-                if (SystemConstant.strNotNull(errorMsg)) {
-                    errorMsg = errorMsg + "、" + logicErrorCommonNotice + String.join("、", logicErrorList);
+                // 题号重复校验
+                String coursePaper = SystemConstant.mergeString(SystemConstant.HYPHEN, courseCode, paperNumber);
+                String questionNumber = SystemConstant.mergeString(SystemConstant.HYPHEN, mainNumber, subNumber);
+                if (coursePaperQuestionMap.containsKey(coursePaper)) {
+                    if (questionNumber.equals(coursePaperQuestionMap.get(coursePaper))) {
+                        errorList.add("存在相同大题号、小题号");
+                        throw ExceptionResultEnum.ERROR.exception(String.join(";", errorList));
+                    }
                 } else {
-                    errorMsg = logicErrorCommonNotice + String.join("、", logicErrorList);
-                }
-                objectiveStructDto.setErrorMsg(errorMsg);
-            } else {
-                MarkQuestion markQuestion = new MarkQuestion();
-                markQuestion.setExamId(examId);
-                markQuestion.setPaperNumber(paperNumber);
-                markQuestion.setPaperType(OptionsEnum.A.getCode());
-                markQuestion.setObjective(true);
-                markQuestion.setMainNumber(mainNumber);
-                markQuestion.setSubNumber(subNumber);
-                markQuestion.setMainTitle(mainTitle);
-                // todo 2024-10-16
-                markQuestion.setAnswer(answer);
-                markQuestion.setOptionCount(optionCount);
-                markQuestion.setTotalScore(totalScore);
-                // todo 2024-10-16
-//                markQuestion.setObjectivePolicy(ObjectivePolicy.NONE);
-                markQuestion.setQuestionType(questionType);
-                markQuestionList.add(markQuestion);
+                    coursePaperQuestionMap.put(coursePaper, questionNumber);
+                }
+
+                // 题号是否被主观题使用
+                if (subjectivePaperNumberSet.contains(SystemConstant.mergeString(SystemConstant.HYPHEN, paperNumber, mainNumber, subNumber))) {
+                    errorList.add("大题号、小题号已被主观题使用");
+                    throw ExceptionResultEnum.ERROR.exception(String.join(";", errorList));
+                }
+
+                // 组装数据
+                assembleObjectiveImportData(examId, objectiveStructDto, markQuestions, markQuestionAnswers, sysUserMap);
+            } catch (Exception e) {
+                hasError = true;
+                objectiveStructDto.setErrorMsg(e.getMessage());
+            } finally {
+                objectiveStructDtos.add(objectiveStructDto);
             }
         }
 
-        if (objectiveStructDtoList.stream().anyMatch(e -> SystemConstant.strNotNull(e.getErrorMsg()))) {
-            // 抛出excel解析异常
-            throw ExceptionResultEnum.ERROR.exception(
-                    objectiveStructDtoList.stream().map(ObjectiveStructDto::getErrorMsg).filter(SystemConstant::strNotNull).collect(Collectors.joining(System.lineSeparator())));
-        } else {
-            // 保存客观题结构
-            Map<String, List<MarkQuestion>> markQuestionMap = markQuestionList.stream().collect(Collectors.groupingBy(MarkQuestion::getPaperNumber));
-            markQuestionMap.forEach((k, v) -> {
-                // 优先删除该试卷编号下的客观题
-                markQuestionService.deleteByExamIdAndPaperNumberAndObjective(examId, k, true);
-                // 保存客观题
-                markQuestionService.saveBatch(v);
-                markQuestionService.updateMarkPaperScore(examId, k);
-
-                // 增加客观题
-                markQuestionAnswerService.deleteByExamIdAndPaperNumber(examId, k);
-                markQuestionAnswerService.saveByMarkQuestion(examId, k, v, true);
-            });
-            // 更改试卷结构状态为已提交
-            Set<String> paperNumberSet = markQuestionMap.keySet();
-            if (CollectionUtils.isNotEmpty(paperNumberSet)) {
-                markPaperService.update(new UpdateWrapper<MarkPaper>().lambda().eq(MarkPaper::getExamId, examId)
-                        .in(MarkPaper::getPaperNumber, paperNumberSet).set(MarkPaper::getQuestionStatus, true));
+        map.put(SystemConstant.ERROR_DATA_LIST, objectiveStructDtos);
+        map.put(SystemConstant.DATASOURCE, markQuestions);
+        map.put(SystemConstant.DATASOURCE1, markQuestionAnswers);
+        map.put(SystemConstant.DATA_COUNT, objectiveStructDtos.size());
+        map.put(SystemConstant.HAS_ERROR_DATA, hasError);
+
+        return map;
+    }
+
+    private void validObjectiveAnswer(String answer, Integer optionCount, Integer questionType) {
+        if (optionCount <= 0 || optionCount > 26) {
+            throw ExceptionResultEnum.ERROR.exception("选项个数只能填1~26之间的整数");
+        }
+        // 选项可选范围
+        List<String> optionScope = OptionsEnum.scopeByOptionCount(optionCount);
+        String optionScopeString = String.join("", optionScope);
+        String[] answers = answer.split("");
+        // 单选题
+        if (questionType == 1) {
+            if (answers.length > 1) {
+                throw ExceptionResultEnum.ERROR.exception("单选题有且仅有一个答案");
+            }
+            for (String s : answers) {
+                if (!optionScope.contains(s)) {
+                    throw ExceptionResultEnum.ERROR.exception("答案只能从[" + optionScopeString + "]中选择一个");
+                }
             }
         }
-        return map;
+        // 多选题
+        else if (questionType == 2) {
+            for (String s : answers) {
+                if (!optionScope.contains(s)) {
+                    throw ExceptionResultEnum.ERROR.exception("答案只能从[" + optionScopeString + "]中选择一个或多个");
+                }
+            }
+        }
+        // 判断题
+        else if (questionType == 3) {
+            if (optionCount != 2) {
+                throw ExceptionResultEnum.ERROR.exception("判断题选项个数只能为2");
+            }
+            if (answers.length > 1) {
+                throw ExceptionResultEnum.ERROR.exception("判断题有且仅有一个答案A或B,A代表正确,B代表错误");
+            }
+            for (String s : answers) {
+                if (!optionScope.contains(s)) {
+                    throw ExceptionResultEnum.ERROR.exception("答案只能从[" + optionScopeString + "]中选择一个");
+                }
+            }
+        } else {
+            throw ExceptionResultEnum.ERROR.exception("题型只能从1、2、3中选择一个填写,1代表单选题,2代表多选题,3代表判断判断题");
+        }
     }
 
     @Transactional
@@ -1148,6 +1165,9 @@ public class ImportLogicServiceImpl implements ImportLogicService {
                 if (StringUtils.isBlank(subjectiveStructDto.getPaperNumber())) {
                     errorList.add("试卷编号不能为空");
                 }
+                if (StringUtils.isBlank(subjectiveStructDto.getMainTitle())) {
+                    errorList.add("大题名称不能为空");
+                }
                 Integer mainNumber = subjectiveStructDto.getMainNumber();
                 if (mainNumber == null) {
                     errorList.add("大题号不能为空");
@@ -1202,7 +1222,7 @@ public class ImportLogicServiceImpl implements ImportLogicService {
 
                 // 试卷编号是否设置了主观题
                 if (subjectivePaperNumberSet.contains(paperNumber)) {
-                    errorList.add("试卷编号设置了主观题");
+                    errorList.add("该试卷编号下已经存在主观题试卷结构,不允许导入");
                     throw ExceptionResultEnum.ERROR.exception(String.join(";", errorList));
                 }
 
@@ -1263,6 +1283,39 @@ public class ImportLogicServiceImpl implements ImportLogicService {
         return map;
     }
 
+    private void assembleObjectiveImportData(Long examId, ObjectiveStructDto objectiveStructDto, List<MarkQuestion> markQuestions, List<MarkQuestionAnswer> markQuestionAnswers, Map<String, SysUserResult> sysUserMap) {
+        MarkQuestion markQuestion = new MarkQuestion();
+        markQuestion.setId(SystemConstant.getDbUuid());
+        markQuestion.setExamId(examId);
+        markQuestion.setCourseId(objectiveStructDto.getCourseId());
+        markQuestion.setPaperNumber(objectiveStructDto.getPaperNumber());
+        markQuestion.setObjective(true);
+        markQuestion.setMainNumber(objectiveStructDto.getMainNumber());
+        markQuestion.setSubNumber(objectiveStructDto.getSubNumber());
+        markQuestion.setMainTitle(objectiveStructDto.getMainTitle());
+        markQuestion.setOptionCount(objectiveStructDto.getOptionCount());
+        markQuestion.setQuestionType(objectiveStructDto.getQuestionType());
+        markQuestion.setTotalScore(objectiveStructDto.getTotalScore());
+        markQuestions.add(markQuestion);
+
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, objectiveStructDto.getPaperNumber());
+        String paperType = markPaper.getPaperType();
+        if (StringUtils.isNotBlank(paperType)) {
+            for (String type : paperType.split(SystemConstant.COMMA)) {
+                MarkQuestionAnswer markQuestionAnswer = new MarkQuestionAnswer();
+                markQuestionAnswer.setId(SystemConstant.getDbUuid());
+                markQuestionAnswer.setExamId(examId);
+                markQuestionAnswer.setPaperNumber(objectiveStructDto.getPaperNumber());
+                markQuestionAnswer.setPaperType(type);
+                markQuestionAnswer.setMainNumber(objectiveStructDto.getMainNumber());
+                markQuestionAnswer.setSubNumber(objectiveStructDto.getSubNumber());
+                markQuestionAnswer.setAnswer(objectiveStructDto.getAnswer());
+                markQuestionAnswer.setObjectivePolicy(ObjectivePolicy.NONE);
+                markQuestionAnswers.add(markQuestionAnswer);
+            }
+        }
+    }
+
     private void assembleSubjectiveImportData(Long examId, SubjectiveStructDto subjectiveStructDto, List<MarkQuestion> markQuestions, List<MarkUserQuestion> markUserQuestions, Map<String, SysUserResult> sysUserMap) {
         MarkQuestion markQuestion = new MarkQuestion();
         markQuestion.setId(SystemConstant.getDbUuid());

+ 404 - 0
distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_2.java

@@ -0,0 +1,404 @@
+package com.qmth.distributed.print.upgrade;
+
+import com.alibaba.fastjson.JSON;
+import com.qmth.boot.data.upgrade.service.DataUpgradeService;
+import com.qmth.boot.data.upgrade.utils.ResourceFileHelper;
+import com.qmth.distributed.print.business.entity.ExamTask;
+import com.qmth.distributed.print.business.entity.ExamTaskDetail;
+import com.qmth.teachcloud.common.bean.vo.PaperInfoVo;
+import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.entity.MarkQuestion;
+import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
+import com.qmth.teachcloud.mark.entity.MarkArchiveStudent;
+import com.qmth.teachcloud.mark.entity.MarkQuestionAnswer;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.DateFormatUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.jdbc.core.BatchPreparedStatementSetter;
+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.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+
+/**
+ * 升级3.4.2后,3.4.1及以前历史数据的处理
+ */
+//@DataUpgradeVersion("3.4.2")
+public class DataUpgrade_3_4_2 implements DataUpgradeService {
+
+    private static final Logger log = LoggerFactory.getLogger(DataUpgrade_3_4_2.class);
+
+    // 一页1000条
+    private static int PAGE_SIZE = 500;
+
+    private static String DEFAULT_A = "A";
+
+    private static String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    @Override
+    public void process(JdbcTemplate jdbcTemplate) {
+        log.info("process...");
+        StringJoiner sj = new StringJoiner(File.separator);
+        sj.add(System.getProperty("user.dir")).add("distributed-print").add("install").add("mysql").add("upgrade");
+        String upgradePath = sj.toString();
+        log.info("数据库脚本根目录:" + upgradePath);
+        // 执行升级脚本
+//        String upgrade_sql1 = upgradePath + File.separator + "3.4.4-upgrade-1.sql";
+//        this.execUpgradeSql(jdbcTemplate, upgrade_sql1);
+
+        // 执行历史数据归档
+//        this.execSerialNumber(jdbcTemplate);
+    }
+
+    private void execSerialNumber(JdbcTemplate jdbcTemplate) {
+        List<ExamTaskDetail> examTaskDetailSaveList = new ArrayList<>();
+        List<ExamTaskDetail> examTaskDetailUpdateList = new ArrayList<>();
+
+        log.info("开始处理历史数据;时间:" + DateFormatUtils.format(new Date(), DATE_PATTERN));
+        List<ExamTaskDetail> examTaskDetails = this.examTaskDetailList(jdbcTemplate);
+        log.info("1.1开始拆分电子交卷上传试卷数据,共" + examTaskDetails.size() + "条数据");
+        // 拆分电子交卷试卷
+        for (ExamTaskDetail examTaskDetail : examTaskDetails) {
+            ExamTask examTask = this.getExamTaskById(jdbcTemplate, examTaskDetail.getExamTaskId());
+            if (examTask == null) {
+                continue;
+            }
+
+            String paperType = examTaskDetail.getPaperType();
+            // 所有卷型
+            String[] paperTypes = paperType.split(",");
+            // 已曝光集合
+            String exposedPaperType = examTaskDetail.getExposedPaperType();
+            List<String> exposePaperTypes = StringUtils.isNotBlank(exposedPaperType) ? Arrays.asList(exposedPaperType.split(",")) : Collections.emptyList();
+            // 已关联集合
+            String relatePaperType = examTaskDetail.getRelatePaperType();
+            List<String> relatePaperTypes = StringUtils.isNotBlank(relatePaperType) ? Arrays.asList(relatePaperType.split(",")) : Collections.emptyList();
+            // 所有上传卷型试卷信息
+            List<PaperInfoVo> paperInfoVoList = examTaskDetail.getPaperInfoVoList();
+            // 只有一个卷型、或者开启AB卷的,直接更新
+            if (paperType.length() == 1 || examTask.getOpenAb()) {
+                examTaskDetail.setSerialNumber(1);
+                examTaskDetail.setExposed(exposePaperTypes.contains(paperType));
+                examTaskDetail.setExposedExamId(examTaskDetail.getExposed() || relatePaperTypes.contains(paperType) ? examTask.getExamId() : null);
+                examTaskDetailUpdateList.add(examTaskDetail);
+            } else {
+                for (String type : paperTypes) {
+                    // 暂时不处理A
+                    if (type.equals(DEFAULT_A)) {
+                        continue;
+                    }
+                    ExamTaskDetail taskDetail = new ExamTaskDetail();
+                    taskDetail.setId(SystemConstant.getDbUuid());
+                    taskDetail.setExamTaskId(examTaskDetail.getExamTaskId());
+                    taskDetail.setSerialNumber(calcSerialNumber(type));
+                    taskDetail.setPaperType(type);
+                    taskDetail.setPaperAttachmentIds(getPaperTypeIds(paperInfoVoList, type));
+                    taskDetail.setExposed(exposePaperTypes.contains(type));
+                    taskDetail.setExposedExamId(taskDetail.getExposed() || relatePaperTypes.contains(type) ? examTask.getExamId() : null);
+                    taskDetail.setCreateId(examTaskDetail.getCreateId());
+                    taskDetail.setCreateTime(examTaskDetail.getCreateTime());
+                    taskDetail.setUpdateId(examTaskDetail.getUpdateId());
+                    taskDetail.setUpdateTime(examTaskDetail.getUpdateTime());
+                    examTaskDetailSaveList.add(taskDetail);
+
+                }
+
+                // 最后处理A卷,将原数据改为A
+                examTaskDetail.setSerialNumber(1);
+                examTaskDetail.setPaperType(DEFAULT_A);
+                examTaskDetail.setPaperAttachmentIds(getPaperTypeIds(paperInfoVoList, DEFAULT_A));
+                examTaskDetail.setExposed(exposePaperTypes.contains(DEFAULT_A));
+                examTaskDetail.setExposedExamId(examTaskDetail.getExposed() || relatePaperTypes.contains(DEFAULT_A) ? examTask.getExamId() : null);
+                examTaskDetailUpdateList.add(examTaskDetail);
+            }
+        }
+        this.saveBatchExamTaskDetail(jdbcTemplate, examTaskDetailSaveList);
+        this.updateBatchExamTaskDetail(jdbcTemplate, examTaskDetailUpdateList);
+        log.info("1.1线束拆分电子交卷上传试卷数据,共" + examTaskDetails.size() + "条数据");
+
+        log.info("1.2开始更新exam_detail_course表中serial_number值");
+        // 更新exam_detail_course表serial_number
+        this.updateExamDetailCourseSerialNumber(jdbcTemplate);
+        log.info("1.2结束更新exam_detail_course表中serial_number值");
+
+        log.info("1.3开始更新mark_paper表中serial_number值");
+        // 更新mark_paper表serial_number
+        this.updateMarkPaperSerialNumber(jdbcTemplate);
+        log.info("1.3结束更新mark_paper表中serial_number值");
+
+        log.info("1.4开始更新mark_paper_package表中serial_number值");
+        // 更新mark_paper_package表serial_number
+        this.updateMarkPaperPackageSerialNumber(jdbcTemplate);
+        log.info("1.4结束更新mark_paper_package表中serial_number值");
+
+        log.info("1.5开始更新mark_student表中serial_number值");
+        // 更新mark_student表serial_number
+        this.updateMarkStudentSerialNumber(jdbcTemplate);
+        log.info("1.5结束更新mark_student表中serial_number值");
+
+        log.info("1.6开始更新scan_answer_card表中serial_number值");
+        // 更新scan_answer_card表serial_number
+        this.updateScanAnswerCardSerialNumber(jdbcTemplate);
+        log.info("1.6结束更新scan_answer_card表中serial_number值");
+
+        log.info("2.1开始拆分mark_question表中客观题标答到mark_question_answer表");
+        // 拆分客观题标答
+        List<MarkQuestionAnswer> markQuestionAnswerList = new ArrayList<>();
+        for (MarkQuestion markQuestion : this.markQuestionList(jdbcTemplate)) {
+            MarkQuestionAnswer markQuestionAnswer = new MarkQuestionAnswer(markQuestion.getExamId(), markQuestion.getPaperNumber(), markQuestion.getPaperType(), markQuestion.getMainNumber(), markQuestion.getSubNumber());
+            markQuestionAnswer.setAnswer(markQuestion.getAnswer());
+            markQuestionAnswer.setObjectivePolicy(markQuestion.getObjectivePolicy());
+            markQuestionAnswer.setObjectivePolicyScore(markQuestion.getObjectivePolicyScore());
+            markQuestionAnswerList.add(markQuestionAnswer);
+        }
+        if (CollectionUtils.isNotEmpty(markQuestionAnswerList)) {
+            this.saveBatchMarkQuestionAnswer(jdbcTemplate, markQuestionAnswerList);
+        }
+        log.info("2.1结束拆分mark_question表中客观题标答到mark_question_answer表");
+        log.info("历史数据处理完成;时间:" + DateFormatUtils.format(new Date(), DATE_PATTERN));
+
+    }
+
+    private void execUpgradeSql(JdbcTemplate jdbcTemplate, String upgradeSql) {
+        if (StringUtils.isBlank(upgradeSql)) {
+            throw new RuntimeException("没有配置升级sql脚本文件地址");
+        }
+        File file = new File(upgradeSql);
+        if (!file.exists()) {
+            throw new RuntimeException("升级sql脚本文件不存在");
+        }
+        try {
+            FileInputStream inputStream = new FileInputStream(upgradeSql);
+            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> T getOne(JdbcTemplate jdbcTemplate, String sql, Class<T> tClass) {
+        //将查询的语句封装到List集合中,集合中存储每一个员工实体
+        T t = null;
+        try {
+            t = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(tClass));
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception("查询失败:" + e.getMessage());
+        }
+        return t;
+    }
+
+    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 int update(JdbcTemplate jdbcTemplate, String sql) {
+        try {
+            return jdbcTemplate.update(sql);
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception("更新失败:" + e.getMessage());
+        }
+    }
+
+    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 List<ExamTaskDetail> examTaskDetailList(JdbcTemplate jdbcTemplate) {
+        String sql = "select * from exam_task_detail where paper_confirm_attachment_ids is not null";
+        return this.listData(jdbcTemplate, sql, ExamTaskDetail.class);
+    }
+
+    private ExamTask getExamTaskById(JdbcTemplate jdbcTemplate, Long examTaskId) {
+        String sql = "select * from exam_task where id = " + examTaskId;
+        return this.getOne(jdbcTemplate, sql, ExamTask.class);
+    }
+
+    private void updateExamDetailCourseSerialNumber(JdbcTemplate jdbcTemplate) {
+        StringBuffer sql = new StringBuffer("");
+        sql.append(" UPDATE exam_detail_course edc                                          ");
+        sql.append("             LEFT JOIN                                                  ");
+        sql.append("             exam_detail ed ON edc.exam_detail_id = ed.id               ");
+        sql.append("             SET                                                        ");
+        sql.append("             edc.serial_number = (SELECT                                ");
+        sql.append("             etd.serial_number                                          ");
+        sql.append("             FROM                                                       ");
+        sql.append("             exam_task_detail etd                                       ");
+        sql.append("             LEFT JOIN                                                  ");
+        sql.append("             exam_task et ON etd.exam_task_id = et.id                   ");
+        sql.append("             WHERE                                                      ");
+        sql.append("             ed.exam_id = et.exam_id                                    ");
+        sql.append("             AND edc.paper_number = et.paper_number                     ");
+        sql.append("             AND edc.paper_type = etd.paper_type)                       ");
+        sql.append("         WHERE                                                          ");
+        sql.append("             edc.serial_number IS NULL                                  ");
+        jdbcTemplate.update(sql.toString());
+    }
+
+    private void updateMarkPaperSerialNumber(JdbcTemplate jdbcTemplate) {
+        String sql = "update mark_paper set serial_number = case paper_type when 'A' then 1 when 'B' then 2 when 'C' then 3 when 'D' then 4 when 'E' then 5 when 'F' then 6 when 'G' then 7 end where serial_number is null";
+        jdbcTemplate.update(sql);
+    }
+
+    private void updateMarkPaperPackageSerialNumber(JdbcTemplate jdbcTemplate) {
+        String sql = "update mark_paper_package set serial_number = case paper_type when 'A' then 1 when 'B' then 2 when 'C' then 3 when 'D' then 4 when 'E' then 5 when 'F' then 6 when 'G' then 7 end where serial_number is null";
+        jdbcTemplate.update(sql);
+    }
+
+    private void updateMarkStudentSerialNumber(JdbcTemplate jdbcTemplate) {
+        String sql = "update mark_student set serial_number = case paper_type when 'A' then 1 when 'B' then 2 when 'C' then 3 when 'D' then 4 when 'E' then 5 when 'F' then 6 when 'G' then 7 end where serial_number is null";
+        jdbcTemplate.update(sql);
+    }
+
+    private void updateScanAnswerCardSerialNumber(JdbcTemplate jdbcTemplate) {
+        String sql = "update scan_answer_card set serial_number = case paper_type when 'A' then 1 when 'B' then 2 when 'C' then 3 when 'D' then 4 when 'E' then 5 when 'F' then 6 when 'G' then 7 end where serial_number is null";
+        jdbcTemplate.update(sql);
+    }
+
+    private List<MarkQuestion> markQuestionList(JdbcTemplate jdbcTemplate) {
+        String sql = "select * from mark_question where objective = true order by exam_id, paper_number";
+        return this.listData(jdbcTemplate, sql, MarkQuestion.class);
+    }
+
+    private long saveBatchExamTaskDetail(JdbcTemplate jdbcTemplate, List<ExamTaskDetail> list) {
+        String sql = "insert into exam_task_detail(id, exam_task_id, serial_number, paper_type, paper_attachment_ids, exposed, exposed_exam_id, create_id, create_time, update_id, update_time) values (?,?,?,?,?,?,?,?,?,?,?)";
+        int[] ints = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
+            @Override
+            public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
+                final ExamTaskDetail t = list.get(i);
+                // 参数赋值的时候,序号是从 1 开始的,这一点要注意
+                preparedStatement.setLong(1, t.getId());
+                preparedStatement.setLong(2, t.getExamTaskId());
+                preparedStatement.setInt(3, t.getSerialNumber());
+                preparedStatement.setString(4, t.getPaperType());
+                preparedStatement.setString(5, t.getPaperAttachmentIds());
+                preparedStatement.setBoolean(6, t.getExposed());
+                preparedStatement.setLong(7, t.getExposedExamId());
+                preparedStatement.setLong(8, t.getCreateId());
+                preparedStatement.setLong(9, t.getCreateTime());
+                preparedStatement.setLong(10, t.getUpdateId());
+                preparedStatement.setLong(11, t.getUpdateTime());
+            }
+
+            @Override
+            public int getBatchSize() {
+                return list.size();
+            }
+        });
+        return Arrays.stream(ints).mapToLong(m -> m).sum();
+    }
+
+    private long updateBatchExamTaskDetail(JdbcTemplate jdbcTemplate, List<ExamTaskDetail> list) {
+        String sql = "update mark_archive_student set serial_number = ?, paper_type = ?, paper_attachment_ids = ?, exposed = ?, exposed_exam_id = ? where id = ?";
+        int[] ints = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
+            @Override
+            public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
+                final ExamTaskDetail t = list.get(i);
+                // 参数赋值的时候,序号是从 1 开始的,这一点要注意
+                preparedStatement.setInt(1, t.getSerialNumber());
+                preparedStatement.setString(2, t.getPaperType());
+                preparedStatement.setString(3, t.getPaperAttachmentIds());
+                preparedStatement.setBoolean(4, t.getExposed());
+                preparedStatement.setLong(5, t.getExposedExamId());
+                preparedStatement.setLong(6, t.getId());
+            }
+
+            @Override
+            public int getBatchSize() {
+                return list.size();
+            }
+        });
+        return Arrays.stream(ints).mapToLong(m -> m).sum();
+    }
+
+    private long saveBatchMarkQuestionAnswer(JdbcTemplate jdbcTemplate, List<MarkQuestionAnswer> list) {
+        String sql = "insert into mark_question_answer(id, exam_id, paper_number, paper_type, main_number, sub_number, answer, objective_policy, objective_policy_score) values (?,?,?,?,?,?,?,?,?)";
+        int[] ints = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
+            @Override
+            public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
+                final MarkQuestionAnswer mas = list.get(i);
+                // 参数赋值的时候,序号是从 1 开始的,这一点要注意
+                preparedStatement.setLong(1, mas.getId());
+                preparedStatement.setLong(2, mas.getExamId());
+                preparedStatement.setString(3, mas.getPaperNumber());
+                preparedStatement.setString(4, mas.getPaperType());
+                preparedStatement.setInt(5, mas.getMainNumber());
+                preparedStatement.setInt(6, mas.getSubNumber());
+                preparedStatement.setString(7, mas.getAnswer());
+                preparedStatement.setString(8, mas.getObjectivePolicy().name());
+                preparedStatement.setDouble(9, mas.getObjectivePolicyScore());
+            }
+
+            @Override
+            public int getBatchSize() {
+                return list.size();
+            }
+        });
+        return Arrays.stream(ints).mapToLong(m -> m).sum();
+    }
+
+    private String getPaperTypeIds(List<PaperInfoVo> paperInfoVoList, String oldPaperType) {
+        PaperInfoVo paperInfoVo = paperInfoVoList.stream().filter(m -> m.getName().equals(oldPaperType)).findFirst().get();
+        paperInfoVo.setName(oldPaperType);
+
+        List<PaperInfoVo> list = new ArrayList<>();
+        list.add(paperInfoVo);
+        return JSON.toJSONString(list);
+    }
+
+    private Integer calcSerialNumber(String paperType) {
+        String[] letter = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"};
+        List<String> letterList = Arrays.asList(letter);
+        return letterList.indexOf(paperType) + 1;
+    }
+}

+ 2 - 4
distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_4_1.java → distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_4.java

@@ -1,7 +1,6 @@
 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.distributed.print.business.entity.ExamCard;
@@ -12,7 +11,6 @@ import com.qmth.teachcloud.common.entity.MarkQuestion;
 import com.qmth.teachcloud.common.entity.SysUser;
 import com.qmth.teachcloud.common.enums.ExamModelEnum;
 import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
-import com.qmth.teachcloud.common.util.ResultUtil;
 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;
@@ -43,9 +41,9 @@ import java.util.stream.Collectors;
  * 升级3.4.4后,3.4.3及以前历史数据的归档
  */
 //@DataUpgradeVersion("3.4.4")
-public class DataUpgrade_3_4_4_1 implements DataUpgradeService {
+public class DataUpgrade_3_4_4 implements DataUpgradeService {
 
-    private static final Logger log = LoggerFactory.getLogger(DataUpgrade_3_4_4_1.class);
+    private static final Logger log = LoggerFactory.getLogger(DataUpgrade_3_4_4.class);
 
     // 一页1000条
     private static int PAGE_SIZE = 500;

+ 2 - 2
distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_4_2.java → distributed-print/src/main/java/com/qmth/distributed/print/upgrade/DataUpgrade_3_4_5.java

@@ -42,9 +42,9 @@ import java.util.stream.Collectors;
  * 升级3.4.4后,新版本数据的归档
  */
 @DataUpgradeVersion("3.4.4")
-public class DataUpgrade_3_4_4_2 implements DataUpgradeService {
+public class DataUpgrade_3_4_5 implements DataUpgradeService {
 
-    private static final Logger log = LoggerFactory.getLogger(DataUpgrade_3_4_4_2.class);
+    private static final Logger log = LoggerFactory.getLogger(DataUpgrade_3_4_5.class);
 
     // 一页1000条
     private static int PAGE_SIZE = 500;

+ 14 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/enums/OptionsEnum.java

@@ -1,5 +1,6 @@
 package com.qmth.teachcloud.common.enums;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -91,4 +92,17 @@ public enum OptionsEnum {
         }
         return result;
     }
+
+    public static List<String> scopeByOptionCount(Integer optionCount) {
+        if (optionCount > 26) {
+            throw ExceptionResultEnum.ERROR.exception("选项数量不能大于26");
+        }
+        List<String> codes = new ArrayList<>();
+        for (OptionsEnum value : OptionsEnum.values()) {
+            if (value.index < optionCount - 1) {
+                codes.add(value.name());
+            }
+        }
+        return codes;
+    }
 }

+ 2 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/mapper/MarkQuestionMapper.java

@@ -18,4 +18,6 @@ import java.util.List;
 public interface MarkQuestionMapper extends BaseMapper<MarkQuestion> {
 
     List<MarkQuestionAnswerVo> listQuestionAnswerByExamIdAndPaperNumberAndPaperType(@Param("examId") Long examId, @Param("paperNumber") String paperNumber, @Param("paperType") String paperType, @Param("objective") Boolean objective);
+
+    int countUnBindMarkerQuestion(@Param("examId") Long examId, @Param("paperNumber") String paperNumber);
 }

+ 4 - 2
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkPaperService.java

@@ -68,8 +68,6 @@ public interface MarkPaperService extends IService<MarkPaper> {
 
     List<MarkPaper> listByExamId(Long examId, MarkPaperStatus status, DataPermissionRule dpr);
 
-    void updateGroupStatusByExamIdAndPaperNumber(boolean groupStatus, Long examId, String paperNumber);
-
     int countByPropositionTeacherId(boolean status);
 
     void updateStudentCountByExamIdAndPaperNumber(Long examId, String paperNumber);
@@ -104,4 +102,8 @@ public interface MarkPaperService extends IService<MarkPaper> {
      * @param response response
      */
     void scoreListExport(ArchiveScoreQuery query, HttpServletResponse response) throws IOException;
+
+    void updateQuestionStatus(Long examId, String paperNumber);
+
+    void updateGroupStatus(Long examId, String paperNumber);
 }

+ 2 - 2
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkQuestionService.java

@@ -74,8 +74,6 @@ public interface MarkQuestionService extends IService<MarkQuestion> {
 
     MarkQuestionDto pageQuestionsByExamIdAndPaperNumber(Long examId, String paperNumber);
 
-    long countByExamIdAndPaperNumberAndObjectiveAndGroupNumberIsNull(Long examId, String paperNumber, boolean objective);
-
     List<MarkQuestion> listByExamIdAndPaperNumberAndObjective(Long examId, String paperNumber, Boolean objective);
 
     void deleteByExamIdAndPaperNumber(Long examId, String paperNumber);
@@ -118,4 +116,6 @@ public interface MarkQuestionService extends IService<MarkQuestion> {
     void updatePic(String content, List<MarkQuestion> markQuestions);
 
     List<MarkQuestion> listByExamIdAndObjective(Long examId, boolean objective);
+
+    int countUnBindMarkerQuestion(Long examId, String paperNumber);
 }

+ 0 - 2
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkService.java

@@ -88,8 +88,6 @@ public interface MarkService {
 
     void calcObjectiveScore(MarkPaper markPaper);
 
-    void updateMarkGroupStatus(Long examId, String paperNumber);
-
     void checkStudentSubjectiveScore(Long examId, String coursePaperId);
 
     boolean rejectMarkTask(MarkTask markTask, Long userId, String reason);

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

@@ -77,7 +77,7 @@ public interface MarkUserQuestionService extends IService<MarkUserQuestion> {
 
     void updateRejectCountByExamIdAndPaperNumberAndQuestionIdAndUserId(Long examId, String paperNumber, Long questionId, Long userId);
 
-    void saveDefaultMarker(Long examId, String paperNumber, List<MarkQuestion> markQuestions);
+    void saveDefaultMarker(Long examId, String paperNumber);
 
     List<String> countClassByExamIdAndPaperNumberAndQuestionId(Long examId, String paperNumber, Long questionId);
 

+ 27 - 10
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkPaperServiceImpl.java

@@ -117,7 +117,6 @@ public class MarkPaperServiceImpl extends ServiceImpl<MarkPaperMapper, MarkPaper
     @Resource
     private MarkFileService markFileService;
 
-
     @Override
     public IPage<MarkSettingDto> listPaperSetting(Long examId, Long openCollegeId, Long courseId, String paperNumber, Boolean groupStatus, Integer pageNumber, Integer pageSize) {
         Page<MarkSettingDto> page = new Page<>(pageNumber, pageSize);
@@ -410,15 +409,6 @@ public class MarkPaperServiceImpl extends ServiceImpl<MarkPaperMapper, MarkPaper
         return this.baseMapper.listByExamId(examId, status.name(), dpr);
     }
 
-    @Override
-    public void updateGroupStatusByExamIdAndPaperNumber(boolean groupStatus, Long examId, String paperNumber) {
-        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkPaper::getGroupStatus, groupStatus)
-                .eq(MarkPaper::getExamId, examId)
-                .eq(MarkPaper::getPaperNumber, paperNumber);
-        this.update(updateWrapper);
-    }
-
     @Override
     public int countByPropositionTeacherId(boolean status) {
         Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
@@ -776,4 +766,31 @@ public class MarkPaperServiceImpl extends ServiceImpl<MarkPaperMapper, MarkPaper
             throw new RuntimeException(e);
         }
     }
+
+    @Override
+    public void updateQuestionStatus(Long examId, String paperNumber) {
+        List<MarkQuestion> markQuestionList = markQuestionService.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
+        boolean status = false;
+        if (CollectionUtils.isNotEmpty(markQuestionList)) {
+            status = markQuestionList.stream().filter(m -> m.getTotalScore() == null || m.getIntervalScore() == null).count() == 0;
+        } else {
+            markQuestionList = markQuestionService.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, true);
+            status = CollectionUtils.isNotEmpty(markQuestionList);
+        }
+        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkPaper::getQuestionStatus, status)
+                .eq(MarkPaper::getExamId, examId)
+                .eq(MarkPaper::getPaperNumber, paperNumber);
+        this.update(updateWrapper);
+    }
+
+    @Override
+    public void updateGroupStatus(Long examId, String paperNumber) {
+        int count = markQuestionService.countUnBindMarkerQuestion(examId, paperNumber);
+        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkPaper::getGroupStatus, count == 0)
+                .eq(MarkPaper::getExamId, examId)
+                .eq(MarkPaper::getPaperNumber, paperNumber);
+        this.update(updateWrapper);
+    }
 }

+ 9 - 14
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkQuestionServiceImpl.java

@@ -242,11 +242,14 @@ public class MarkQuestionServiceImpl extends ServiceImpl<MarkQuestionMapper, Mar
                 .eq(MarkPaper::getPaperNumber, paperNumber);
         markPaperService.update(updateWrapper);
 
+        // 更新评卷员绑定状态
+        markPaperService.updateGroupStatus(examId, paperNumber);
+        // 命题老师,任课老师,默认为评卷员
+        markUserQuestionService.saveDefaultMarker(examId, paperNumber);
+
         // 主观题结构有变动
         List<MarkQuestion> subjectiveQuestions = questions.stream().filter(m -> m.getObjective()).collect(Collectors.toList());
         List<MarkQuestion> subjectiveMarkQuestionList = markQuestionList.stream().filter(m -> m.getObjective()).collect(Collectors.toList());
-        // 更新分组状态
-        markService.updateMarkGroupStatus(examId, paperNumber);
 
         if (!CollectionUtils.isEqualCollection(subjectiveQuestions, subjectiveMarkQuestionList)) {
             // 考生主观题重新统分
@@ -508,18 +511,6 @@ public class MarkQuestionServiceImpl extends ServiceImpl<MarkQuestionMapper, Mar
         return markQuestionDto;
     }
 
-    @Override
-    public long countByExamIdAndPaperNumberAndObjectiveAndGroupNumberIsNull(Long examId, String paperNumber, boolean objective) {
-        // todo 2025-02-25
-//        QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
-//        queryWrapper.lambda().eq(MarkQuestion::getExamId, examId)
-//                .eq(MarkQuestion::getPaperNumber, paperNumber)
-//                .eq(MarkQuestion::getObjective, objective)
-//                .isNull(MarkQuestion::getGroupNumber);
-//        return this.count(queryWrapper);
-        return 0;
-    }
-
     @Override
     public List<MarkQuestion> listByExamIdAndPaperNumberAndObjective(Long examId, String paperNumber, Boolean objective) {
         QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
@@ -881,4 +872,8 @@ public class MarkQuestionServiceImpl extends ServiceImpl<MarkQuestionMapper, Mar
         return this.list(queryWrapper);
     }
 
+    @Override
+    public int countUnBindMarkerQuestion(Long examId, String paperNumber) {
+        return this.baseMapper.countUnBindMarkerQuestion(examId, paperNumber);
+    }
 }

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

@@ -1373,14 +1373,6 @@ public class MarkServiceImpl implements MarkService {
         return history;
     }
 
-    @Override
-    public void updateMarkGroupStatus(Long examId, String paperNumber) {
-        // 更新MarkPaper中groupStatus
-        long groupNumberIsNull = markQuestionService.countByExamIdAndPaperNumberAndObjectiveAndGroupNumberIsNull(examId,
-                paperNumber, false);
-        markPaperService.updateGroupStatusByExamIdAndPaperNumber(groupNumberIsNull == 0, examId, paperNumber);
-    }
-
     @Override
     public void checkStudentSubjectiveScore(Long examId, String coursePaperId) {
         List<MarkStudent> markStudentList = markStudentService.listByExamIdAndCoursePaperId(examId, coursePaperId);

+ 9 - 2
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkUserQuestionServiceImpl.java

@@ -394,12 +394,19 @@ public class MarkUserQuestionServiceImpl extends ServiceImpl<MarkUserQuestionMap
     }
 
     @Override
-    public void saveDefaultMarker(Long examId, String paperNumber, List<MarkQuestion> markQuestions) {
+    public void saveDefaultMarker(Long examId, String paperNumber) {
+        List<MarkQuestion> markQuestions = markQuestionService.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
+        if (CollectionUtils.isEmpty(markQuestions)) {
+            return;
+        }
         List<SysUser> sysUserList = this.baseMapper.listDefaultMarkerByExamIdAndPaperNumber(examId, paperNumber);
         List<MarkUserQuestion> markUserQuestions = new ArrayList<>();
         for (SysUser sysUser : sysUserList) {
             for (MarkQuestion markQuestion : markQuestions) {
-                markUserQuestions.add(new MarkUserQuestion(examId, paperNumber, markQuestion.getId(), sysUser.getId()));
+                MarkUserQuestion markUserQuestion = this.getByExamIdAndPaperNumberAndQuestionIdAndUserId(examId, paperNumber, markQuestion.getId(), sysUser.getId());
+                if (markUserQuestion == null) {
+                    markUserQuestions.add(new MarkUserQuestion(examId, paperNumber, markQuestion.getId(), sysUser.getId()));
+                }
             }
         }
         this.saveBatch(markUserQuestions);

+ 17 - 0
teachcloud-mark/src/main/resources/mapper/MarkQuestionMapper.xml

@@ -47,4 +47,21 @@
                 AND mq.sub_number = mqa.sub_number
         order by mq.main_number, mq.sub_number
     </select>
+    <select id="countUnBindMarkerQuestion" resultType="java.lang.Integer">
+        SELECT
+            COUNT(1)
+        FROM
+            mark_question mq
+        WHERE
+            mq.exam_id = #{examId} AND mq.paper_number = #{paperNumber}
+          AND mq.objective = FALSE
+          AND NOT EXISTS( SELECT
+                              1
+                          FROM
+                              mark_user_question muq
+                          WHERE
+                              muq.exam_id = #{examId} AND muq.paper_number = #{paperNumber}
+                            AND muq.enable = 1
+                            AND muq.question_id = mq.id)
+    </select>
 </mapper>