Bläddra i källkod

阅卷待办短信修改

wangliang 3 månader sedan
förälder
incheckning
1f8e309714

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

@@ -152,6 +152,7 @@ public class BasicMessageServiceImpl extends ServiceImpl<BasicMessageMapper, Bas
      * @param paperNumber
      */
     @Override
+    @Transactional
     public void smsMarkTask(Long examId, Long courseId, String paperNumber) {
         List<MarkTaskSmsDto> markTaskSmsDtoList = markUserQuestionService.findMarkTaskSms(examId, courseId, paperNumber);
         if (CollectionUtils.isNotEmpty(markTaskSmsDtoList)) {

+ 279 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/dto/BasicMessageDto.java

@@ -0,0 +1,279 @@
+package com.qmth.teachcloud.mark.dto;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.qmth.teachcloud.common.base.BaseEntity;
+import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.enums.MessageEnum;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * @Description: 短信消息表
+ * @Author: CaoZixuan
+ * @Date: 2021-04-28
+ */
+public class BasicMessageDto extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "学校id")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long schoolId;
+
+    @ApiModelProperty(value = "机构id")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long orgId;
+
+    /**
+     * 用户ID
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long userId;
+
+    /**
+     * 用户名称
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private String userName;
+
+    /**
+     * 手机号
+     */
+    private String mobileNumber;
+
+    /**
+     * 试卷编号
+     */
+    private String paperNumber;
+
+    /**
+     * 课程代码
+     */
+    private String courseCode;
+
+    /**
+     * 业务id
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long businessId;
+
+    /**
+     * 业务操作
+     */
+    private String businessOperate;
+
+    /**
+     * 消息模板代码
+     */
+    private String templateCode;
+
+    /**
+     * 变量参数内容
+     */
+    private String variableParams;
+
+    /**
+     * 模板内容
+     */
+    private String templateContent;
+
+    /**
+     * 消息类型
+     */
+    private MessageEnum messageType;
+
+    /**
+     * 消息发送状态
+     */
+    private String sendStatus;
+
+    /**
+     * 消息发送结果
+     */
+    private String sendResult;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 失败重发次数
+     */
+    private Integer resendCount;
+
+    public BasicMessageDto() {
+
+    }
+
+    public BasicMessageDto(Long schoolId, Long userId, String userName, String mobileNumber, String paperNumber,
+                           String courseCode, String variableParams, MessageEnum messageType, Long createId,
+                           String remark, String templateCode, String sendStatus, String message) {
+        this.setSendStatus(sendStatus);
+        this.setSendResult(message);
+        this.setId(SystemConstant.getDbUuid());
+        this.setSchoolId(schoolId);
+        this.setUserId(userId);
+        this.setUserName(userName);
+        this.setMobileNumber(mobileNumber);
+        this.setPaperNumber(paperNumber);
+        this.setCourseCode(courseCode);
+        this.setTemplateCode(templateCode);
+        this.setVariableParams(variableParams);
+        this.setMessageType(messageType);
+        this.setBusinessOperate(messageType.getName());
+        this.setRemark(remark);
+        this.setResendCount(0);
+        this.setCreateId(createId);
+        this.setCreateTime(System.currentTimeMillis());
+    }
+
+    public void setErrorInfo(String message) {
+        this.setSendStatus("SYSTEM_ERROR");
+        this.setSendResult(message);
+    }
+
+    public static long getSerialVersionUID() {
+        return serialVersionUID;
+    }
+
+    public Long getSchoolId() {
+        return schoolId;
+    }
+
+    public void setSchoolId(Long schoolId) {
+        this.schoolId = schoolId;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getMobileNumber() {
+        return mobileNumber;
+    }
+
+    public void setMobileNumber(String mobileNumber) {
+        this.mobileNumber = mobileNumber;
+    }
+
+    public String getPaperNumber() {
+        return paperNumber;
+    }
+
+    public void setPaperNumber(String paperNumber) {
+        this.paperNumber = paperNumber;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public Long getBusinessId() {
+        return businessId;
+    }
+
+    public void setBusinessId(Long businessId) {
+        this.businessId = businessId;
+    }
+
+    public String getBusinessOperate() {
+        return businessOperate;
+    }
+
+    public void setBusinessOperate(String businessOperate) {
+        this.businessOperate = businessOperate;
+    }
+
+    public String getTemplateCode() {
+        return templateCode;
+    }
+
+    public void setTemplateCode(String templateCode) {
+        this.templateCode = templateCode;
+    }
+
+    public String getVariableParams() {
+        return variableParams;
+    }
+
+    public void setVariableParams(String variableParams) {
+        this.variableParams = variableParams;
+    }
+
+    public String getTemplateContent() {
+        return templateContent;
+    }
+
+    public void setTemplateContent(String templateContent) {
+        this.templateContent = templateContent;
+    }
+
+    public MessageEnum getMessageType() {
+        return messageType;
+    }
+
+    public void setMessageType(MessageEnum messageType) {
+        this.messageType = messageType;
+    }
+
+    public String getSendStatus() {
+        return sendStatus;
+    }
+
+    public void setSendStatus(String sendStatus) {
+        this.sendStatus = sendStatus;
+    }
+
+    public String getSendResult() {
+        return sendResult;
+    }
+
+    public void setSendResult(String sendResult) {
+        this.sendResult = sendResult;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public Integer getResendCount() {
+        return resendCount;
+    }
+
+    public void setResendCount(Integer resendCount) {
+        this.resendCount = resendCount;
+    }
+}

+ 19 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/mapper/MarkUserQuestionMapper.java

@@ -7,6 +7,7 @@ import com.qmth.teachcloud.common.bean.dto.MarkTaskSmsDto;
 import com.qmth.teachcloud.common.bean.dto.mark.MarkUser;
 import com.qmth.teachcloud.common.entity.MarkQuestion;
 import com.qmth.teachcloud.common.entity.SysUser;
+import com.qmth.teachcloud.mark.dto.BasicMessageDto;
 import com.qmth.teachcloud.mark.dto.mark.entrance.MarkEntranceDto;
 import com.qmth.teachcloud.mark.dto.mark.manage.MarkQualityDto;
 import com.qmth.teachcloud.mark.dto.mark.manage.MarkUserGroupProgressDto;
@@ -52,4 +53,22 @@ public interface MarkUserQuestionMapper extends BaseMapper<MarkUserQuestion> {
      * @return
      */
     List<MarkTaskSmsDto> findMarkTaskSms(@Param("examId") Long examId, @Param("courseId") Long courseId, @Param("paperNumber") String paperNumber);
+
+    /**
+     * 短信count
+     *
+     * @param userId
+     * @param mobileNumber
+     * @param condition
+     * @return
+     */
+    int smsConditionCount(@Param("userId") Long userId, @Param("mobileNumber") String mobileNumber, @Param("condition") String condition);
+
+    /**
+     * 插入短信消息
+     *
+     * @param basicMessageDto
+     * @return
+     */
+    int insertSms(@Param("basicMessageDto") BasicMessageDto basicMessageDto);
 }

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

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.qmth.teachcloud.common.bean.dto.MarkTaskSmsDto;
 import com.qmth.teachcloud.common.bean.dto.mark.MarkUser;
 import com.qmth.teachcloud.common.entity.MarkQuestion;
+import com.qmth.teachcloud.common.enums.MessageEnum;
 import com.qmth.teachcloud.common.enums.mark.MarkPaperStatus;
 import com.qmth.teachcloud.mark.bean.marker.MarkerAddParam;
 import com.qmth.teachcloud.mark.dto.mark.entrance.MarkEntranceDto;
@@ -92,4 +93,39 @@ public interface MarkUserQuestionService extends IService<MarkUserQuestion> {
      * @return
      */
     List<MarkTaskSmsDto> findMarkTaskSms(Long examId, Long courseId, String paperNumber);
+
+    /**
+     * 短信count
+     *
+     * @param userId
+     * @param mobileNumber
+     * @param condition
+     * @return
+     */
+    int smsConditionCount(Long userId, String mobileNumber, String condition);
+
+    /**
+     * 保存message和备注
+     *
+     * @param schoolId
+     * @param userId
+     * @param userName
+     * @param mobileNumber
+     * @param paperNumber
+     * @param courseCode
+     * @param variableParams
+     * @param messageType
+     * @param createId
+     * @param remark
+     */
+    void saveMessageSendLogRemark(Long schoolId, Long userId, String userName, String mobileNumber, String paperNumber, String courseCode, String variableParams, MessageEnum messageType, Long createId, String remark);
+
+    /**
+     * 发送阅卷待办短信
+     *
+     * @param examId
+     * @param courseId
+     * @param paperNumber
+     */
+    void smsMarkTask(Long examId, Long courseId, String paperNumber);
 }

+ 868 - 863
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkQuestionServiceImpl.java

@@ -1,863 +1,868 @@
-package com.qmth.teachcloud.mark.service.impl;
-
-import com.alibaba.fastjson.JSON;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.qmth.teachcloud.common.bean.dto.mark.MarkUser;
-import com.qmth.teachcloud.common.bean.dto.mark.ObjectiveAnswerDto;
-import com.qmth.teachcloud.common.bean.dto.mark.PictureConfig;
-import com.qmth.teachcloud.common.bean.params.mark.group.QuestionPictureConfigParams;
-import com.qmth.teachcloud.common.bean.result.ExcelResult;
-import com.qmth.teachcloud.common.bean.vo.FilePathVo;
-import com.qmth.teachcloud.common.contant.SystemConstant;
-import com.qmth.teachcloud.common.entity.MarkQuestion;
-import com.qmth.teachcloud.common.entity.SysUser;
-import com.qmth.teachcloud.common.enums.*;
-import com.qmth.teachcloud.common.service.TeachcloudCommonService;
-import com.qmth.teachcloud.common.util.ConvertUtil;
-import com.qmth.teachcloud.common.util.ExcelUtil;
-import com.qmth.teachcloud.common.util.FileStoreUtil;
-import com.qmth.teachcloud.common.util.ServletUtil;
-import com.qmth.teachcloud.mark.bean.mark.DoubleMarkParam;
-import com.qmth.teachcloud.mark.bean.student.MarkStudentQuery;
-import com.qmth.teachcloud.mark.bean.vo.parseCard.pic.SubPictureConfig;
-import com.qmth.teachcloud.mark.dto.mark.MarkQuestionAnswerVo;
-import com.qmth.teachcloud.mark.dto.mark.MarkStudentVo;
-import com.qmth.teachcloud.mark.dto.mark.MarkPaperFileDto;
-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.enums.LockType;
-import com.qmth.teachcloud.mark.enums.MarkTaskStatus;
-import com.qmth.teachcloud.mark.lock.LockService;
-import com.qmth.teachcloud.mark.mapper.MarkQuestionMapper;
-import com.qmth.teachcloud.mark.params.MarkObjectiveQuestionParams;
-import com.qmth.teachcloud.mark.params.MarkQuestionParams;
-import com.qmth.teachcloud.mark.service.*;
-import com.qmth.teachcloud.mark.utils.Calculator;
-import com.qmth.teachcloud.mark.utils.CardParseUtils;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.multipart.MultipartFile;
-
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletResponse;
-import java.math.BigDecimal;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * <p>
- * 小题信息表 服务实现类
- * </p>
- *
- * @author xf
- * @since 2023-09-22
- */
-@Service
-public class MarkQuestionServiceImpl extends ServiceImpl<MarkQuestionMapper, MarkQuestion> implements MarkQuestionService {
-
-    @Resource
-    private TeachcloudCommonService teachcloudCommonService;
-    @Resource
-    private MarkPaperService markPaperService;
-    @Resource
-    private MarkQuestionAnswerService markQuestionAnswerService;
-    @Resource
-    private MarkUserQuestionService markUserQuestionService;
-    @Resource
-    private MarkUserClassService markUserClassService;
-    @Resource
-    private MarkStudentService markStudentService;
-    @Resource
-    private MarkTaskService markTaskService;
-    @Resource
-    private MarkArbitrateHistoryService markArbitrateHistoryService;
-    @Resource
-    private MarkService markService;
-    @Resource
-    private FileStoreUtil fileStoreUtil;
-    @Resource
-    private LockService lockService;
-    @Resource
-    private MarkSyncService markSyncService;
-
-    @Override
-    public String assembleGroupQuestionsByExamIdAndPaperNumberAndNumber(Long examId, String paperNumber, Integer groupNumber) {
-        return this.baseMapper.assembleQuestionsByExamIdAndPaperNumberAndNumber(examId, paperNumber, groupNumber);
-    }
-
-    @Override
-    public List<MarkQuestion> listQuestionByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
-        LambdaQueryWrapper<MarkQuestion> lambdaQueryWrapper = queryWrapper.lambda();
-        lambdaQueryWrapper.eq(MarkQuestion::getExamId, examId)
-                .eq(MarkQuestion::getPaperNumber, paperNumber);
-        lambdaQueryWrapper.orderByAsc(MarkQuestion::getMainNumber)
-                .orderByAsc(MarkQuestion::getSubNumber);
-        return this.list(queryWrapper);
-    }
-
-    @Transactional
-    @Override
-    public void saveQuestions(MarkQuestionParams markQuestionParams) {
-        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
-        Long examId = markQuestionParams.getExamId();
-        String paperNumber = markQuestionParams.getPaperNumber();
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-
-        List<MarkQuestion> questions = markQuestionParams.getQuestions();
-        if (CollectionUtils.isEmpty(questions)) {
-            throw ExceptionResultEnum.ERROR.exception("没有可保存的结构数据");
-        }
-
-        // 校验题号不能重复
-        Map<String, Long> longMap = questions.stream().collect(Collectors.groupingBy(m -> m.getMainNumber() + SystemConstant.HYPHEN + m.getSubNumber(), Collectors.counting()));
-        long count = longMap.entrySet().stream().filter(m -> m.getValue().intValue() > 1).count();
-        if (count > 0) {
-            throw ExceptionResultEnum.ERROR.exception("大题号-小题号有重复数据");
-        }
-
-        // 查询已有结构信息
-        List<MarkQuestion> markQuestionList = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber);
-        if (CollectionUtils.isEmpty(markQuestionList)) {
-            for (MarkQuestion question : questions) {
-                if (question.getTotalScore() < 0) {
-                    throw ExceptionResultEnum.ERROR.exception("小题满分不能小于0");
-                }
-                if (!question.getObjective() && (question.getIntervalScore() == null || question.getIntervalScore() <= 0)) {
-                    throw ExceptionResultEnum.ERROR.exception("间隔分必须大于0");
-                }
-                question.setExamId(examId);
-                question.setPaperNumber(paperNumber);
-                question.setPaperType(markPaper.getPaperType());
-                question.setCreateId(sysUser.getId());
-                question.setCreateTime(System.currentTimeMillis());
-                // 用来判断是否保存过
-                question.setUpdateId(sysUser.getId());
-                question.setUpdateTime(System.currentTimeMillis());
-            }
-            this.saveBatch(questions);
-            markQuestionAnswerService.deleteByExamIdAndPaperNumber(examId, paperNumber);
-            markQuestionAnswerService.saveByMarkQuestion(examId, paperNumber, questions, false);
-        } else {
-            List<MarkQuestion> saveOrUpdateList = new ArrayList<>();
-            for (MarkQuestion question : questions) {
-                if (Objects.isNull(question.getId())) {
-                    question.setId(SystemConstant.getDbUuid());
-                    question.setExamId(examId);
-                    question.setPaperNumber(paperNumber);
-                    question.setPaperType(markPaper.getPaperType());
-                    saveOrUpdateList.add(question);
-                } else {
-                    Optional<MarkQuestion> questionOptional = markQuestionList.stream().filter(m -> m.getId().equals(question.getId())).findFirst();
-                    if (questionOptional.isPresent()) {
-                        MarkQuestion markQuestion = questionOptional.get();
-                        if (markQuestion.getMarkedCount() != null && markQuestion.getMarkedCount() > 0 && markQuestion.getTotalScore() != null && markQuestion.getTotalScore().doubleValue() - question.getTotalScore().doubleValue() != 0) {
-                            throw ExceptionResultEnum.ERROR.exception("大题号" + markQuestion.getMainNumber() + "小题号" + markQuestion.getSubNumber() + "已阅卷,不能修改小题满分");
-                        }
-                        // 主观题改客观题
-                        if (markQuestion.getMarkedCount() != null && markQuestion.getMarkedCount() > 0 && !markQuestion.getObjective() && question.getObjective()) {
-                            throw ExceptionResultEnum.ERROR.exception("大题号" + markQuestion.getMainNumber() + "小题号" + markQuestion.getSubNumber() + "已阅卷,不能修改为客观题题型");
-                        }
-                        if (question.getTotalScore() < 0) {
-                            throw ExceptionResultEnum.ERROR.exception("小题满分不能小于0");
-                        }
-                        if (!QuestionType.findByValue(question.getQuestionType()).getObjective() && question.getIntervalScore() <= 0) {
-                            throw ExceptionResultEnum.ERROR.exception("间隔分必须大于0");
-                        }
-                        // 客观题变主观题
-                        if (markQuestion.getObjective() && !question.getObjective()) {
-                            // 清除判分策略
-                            markQuestionAnswerService.deleteByExamIdAndPaperNumberAndMainNumberAndSubNumber(examId, paperNumber, markQuestion.getMainNumber(), markQuestion.getSubNumber());
-                        }
-                        // 客观题
-                        if (question.getObjective()) {
-                            markQuestion.setOptionCount(question.getOptionCount());
-                            if (!markQuestion.getQuestionType().equals(question.getQuestionType())) {
-                                // 清除判分策略
-                                markQuestionAnswerService.resetObjectivePolicyByExamIdAndPaperNumberAndMainNumberAndSubNumber(examId, paperNumber, markQuestion.getMainNumber(), markQuestion.getSubNumber());
-                            }
-                        }
-                        // 大题号
-                        markQuestion.setMainNumber(question.getMainNumber());
-                        // 小题号
-                        markQuestion.setSubNumber(question.getSubNumber());
-                        // 大题名称
-                        markQuestion.setMainTitle(question.getMainTitle());
-                        // 题型
-                        markQuestion.setQuestionType(question.getQuestionType());
-                        markQuestion.setObjective(question.getObjective());
-                        markQuestion.setTotalScore(question.getTotalScore());
-                        markQuestion.setIntervalScore(question.getIntervalScore());
-                        // 用来判断是否保存过
-                        markQuestion.setUpdateId(sysUser.getId());
-                        markQuestion.setUpdateTime(System.currentTimeMillis());
-                        saveOrUpdateList.add(markQuestion);
-                        markQuestionList.remove(markQuestion);
-                    } else {
-                        question.setId(SystemConstant.getDbUuid());
-                        question.setExamId(examId);
-                        question.setPaperNumber(paperNumber);
-                        question.setPaperType(markPaper.getPaperType());
-                        question.setCreateId(sysUser.getId());
-                        question.setCreateTime(System.currentTimeMillis());
-                        // 用来判断是否保存过
-                        question.setUpdateId(sysUser.getId());
-                        question.setUpdateTime(System.currentTimeMillis());
-                        saveOrUpdateList.add(question);
-                    }
-                }
-            }
-            // 删除的结构
-            if (CollectionUtils.isNotEmpty(markQuestionList)) {
-                List<Long> deleteIds = markQuestionList.stream().map(MarkQuestion::getId).collect(Collectors.toList());
-                this.removeByIds(deleteIds);
-
-                markQuestionList.stream().filter(m -> m.getObjective()).forEach(m -> {
-                    markQuestionAnswerService.deleteByExamIdAndPaperNumberAndMainNumberAndSubNumber(examId, paperNumber, m.getMainNumber(), m.getSubNumber());
-                });
-            }
-            this.saveOrUpdateBatch(saveOrUpdateList);
-            markQuestionAnswerService.saveByMarkQuestion(examId, paperNumber, saveOrUpdateList, false);
-        }
-        // 更新客观题满分、主观题满分、总分
-        List<MarkQuestion> markQuestions = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber);
-        Double objectiveScore = markQuestions.stream().filter(m -> m.getObjective()).collect(Collectors.summingDouble(m -> m.getTotalScore()));
-        Double subjectiveScore = markQuestions.stream().filter(m -> !m.getObjective()).collect(Collectors.summingDouble(m -> m.getTotalScore()));
-        Double totalScore = markQuestions.stream().collect(Collectors.summingDouble(m -> m.getTotalScore()));
-        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkPaper::getObjectiveScore, objectiveScore)
-                .set(MarkPaper::getSubjectiveScore, subjectiveScore)
-                .set(MarkPaper::getTotalScore, totalScore)
-                .set(MarkPaper::getQuestionStatus, true)
-                .eq(MarkPaper::getExamId, examId)
-                .eq(MarkPaper::getPaperNumber, paperNumber);
-        markPaperService.update(updateWrapper);
-
-        // 主观题结构有变动
-        List<MarkQuestion> subjectiveQuestions = questions.stream().filter(m -> m.getObjective()).collect(Collectors.toList());
-        List<MarkQuestion> subjectiveMarkQuestionList = markQuestionList.stream().filter(m -> m.getObjective()).collect(Collectors.toList());
-        if (!CollectionUtils.isEqualCollection(subjectiveQuestions, subjectiveMarkQuestionList)) {
-            // 未分组的题目
-//            long unGroupQuestionCount = this.countByExamIdAndPaperNumberAndObjectiveAndGroupNumberIsNull(examId, paperNumber, false);
-//            // 考生主观题重新统分
-//            markService.checkStudentSubjectiveScore(examId, markPaper.getCoursePaperId(), unGroupQuestionCount);
-        }
-
-        // 更新分组状态
-        markService.updateMarkGroupStatus(examId, paperNumber);
-    }
-
-    @Override
-    public void objectiveAnswerSettingModelExport(Long examId, String paperNumber, String paperType, HttpServletResponse response)
-            throws Exception {
-        List<MarkQuestionAnswerVo> datasource = this.listQuestionAnswerByExamIdAndPaperNumberAndPaperType(examId, paperNumber,
-                paperType, true);
-        List<ObjectiveAnswerDto> objectiveAnswerDtoList = datasource.stream().flatMap(e -> {
-            ObjectiveAnswerDto dto = new ObjectiveAnswerDto();
-            dto.setMainNumber(String.valueOf(e.getMainNumber()));
-            dto.setSubNumber(String.valueOf(e.getSubNumber()));
-            dto.setMainTitle(e.getMainTitle());
-            dto.setAnswer(e.getAnswer());
-            return Stream.of(dto);
-        }).collect(Collectors.toList());
-
-        // 生成excel文件
-        ExcelUtil.excelExport("客观题标答模板", ObjectiveAnswerDto.class, objectiveAnswerDtoList, response);
-    }
-
-    @Transactional
-    @Override
-    public void objectiveAnswerSettingImport(Long examId, String paperNumber, String paperType, MultipartFile file) throws Exception {
-        // 试卷结构
-        List<MarkQuestionAnswer> datasource = markQuestionAnswerService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, paperType);
-        ExcelResult<ObjectiveAnswerDto> excelResult = ConvertUtil.analyzeExcel(file.getInputStream(), ObjectiveAnswerDto.class, false, 0);
-        if (!excelResult.isSuccess()) {
-            throw ExceptionResultEnum.ERROR.exception(excelResult.getErrorMsg());
-        }
-        // 逻辑异常
-        List<String> logicErrorList = new ArrayList<>();
-        List<ObjectiveAnswerDto> objectiveAnswerDtoList = excelResult.getDatasource();
-        if (!Objects.equals(datasource.size(), objectiveAnswerDtoList.size())) {
-            logicErrorList.add("试卷结构试题数量和导入文件中的试题数量不一致");
-        }
-        Set<String> questionNumberSet = new HashSet<>();
-        Map<String, MarkQuestion> markQuestionMap = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber).stream().collect(Collectors.toMap(k -> k.getMainNumber() + SystemConstant.HYPHEN + k.getSubNumber(), v -> v));
-        // 构建题号-试题结构map
-        Map<String, MarkQuestionAnswer> markQuestionAnswerMapMap = datasource.stream().collect(Collectors.toMap(k -> k.getMainNumber() + SystemConstant.HYPHEN + k.getSubNumber(), v -> v));
-
-        List<MarkQuestionAnswer> willEditList = new ArrayList<>();
-        for (int i = 0; i < objectiveAnswerDtoList.size(); i++) {
-            int rowIndex = i + 2;
-            ObjectiveAnswerDto cell = objectiveAnswerDtoList.get(i);
-            int exMainNumber = Integer.parseInt(cell.getMainNumber());
-            int exSubNumber = Integer.parseInt(cell.getSubNumber());
-            String exMainTitle = cell.getMainTitle();
-            String exAnswer = cell.getAnswer();
-
-            String key = exMainNumber + SystemConstant.HYPHEN + exSubNumber;
-            String unifyNotice = String.format("第[%s]行,大题号[%s],小题号[%s]的试题", rowIndex, exMainNumber, exSubNumber);
-            List<String> rowException = new ArrayList<>();
-            if (questionNumberSet.contains(key)) {
-                // 题号重复检验
-                rowException.add("题号和前面重复");
-            } else {
-                questionNumberSet.add(key);
-                if (markQuestionAnswerMapMap.containsKey(key)) {
-                    MarkQuestionAnswer markQuestion = markQuestionAnswerMapMap.get(key);
-                    markQuestion.setAnswer(exAnswer);
-                    willEditList.add(markQuestion);
-                    String mainTitle = markQuestion.getMainTitle();
-                    if (!Objects.equals(exMainTitle, mainTitle)) {
-                        rowException.add(String.format("大题名称[%s]错误", exMainTitle));
-                    } else {
-                        Integer questionType = markQuestionMap.get(key).getQuestionType();
-                        Integer optionCount = markQuestionMap.get(key).getOptionCount();
-                        List<String> optionScope = new ArrayList<>();
-                        try {
-                            for (int j = 1; j <= optionCount; j++) {
-                                String code = OptionsEnum.getCodeByIndex(j);
-                                optionScope.add(code);
-                            }
-                        } catch (Exception e) {
-                            throw ExceptionResultEnum.ERROR.exception(
-                                    String.format("第[%s]行,大题号[%s],小题号[%s]的试题答案可选选项解析失败", rowIndex, exMainNumber,
-                                            exSubNumber));
-                        }
-
-                        // 答案选项越界校验
-                        List<String> answerList = Arrays.asList(exAnswer.split(""));
-                        if (CollectionUtils.isNotEmpty(answerList)) {
-                            for (String answer : answerList) {
-                                String outOfBoundsException = String.format("答案[%s]错误。答案只能从[%s]中选择", answer, String.join("", optionScope));
-                                try {
-                                    OptionsEnum optionsEnum = OptionsEnum.getByCode(answer);
-                                    int optionIndex = optionsEnum.getIndex();
-                                    if (optionIndex > optionCount) {
-                                        // 答案选项超出范围
-                                        rowException.add(outOfBoundsException);
-                                    }
-                                } catch (Exception e) {
-                                    rowException.add(outOfBoundsException);
-                                }
-                            }
-                        } else {
-                            rowException.add("答案必填");
-                        }
-
-                        // 答案和题型匹配校验
-                        if (Objects.equals(questionType, 1)) {
-                            // 单选题
-                            if (answerList.size() > 1) {
-                                rowException.add(String.format("为[单选题],答案[%s]错误", exAnswer));
-                            }
-                        } else if (Objects.equals(questionType, 2)) {
-                            // 多选题
-
-                        } else if (Objects.equals(questionType, 3)) {
-                            // 判断题
-                            if (answerList.size() > 1) {
-                                rowException.add(String.format("为[判断题],答案[%s]错误", exAnswer));
-                            }
-                        } else {
-                            rowException.add("试题题型错误");
-                        }
-                    }
-
-                } else {
-                    rowException.add("题号错误,试卷中不存在该题号");
-                }
-            }
-            if (CollectionUtils.isNotEmpty(rowException)) {
-                unifyNotice = unifyNotice + String.join(",", rowException);
-                logicErrorList.add(unifyNotice);
-            }
-        }
-        if (CollectionUtils.isNotEmpty(logicErrorList)) {
-            throw ExceptionResultEnum.ERROR.exception(String.join(";\n", logicErrorList));
-        }
-        markQuestionAnswerService.saveOrUpdateBatch(willEditList);
-    }
-
-    @Transactional
-    @Override
-    public void saveObjectiveQuestions(MarkObjectiveQuestionParams markObjectiveQuestionParams) {
-        for (MarkQuestionAnswer answer : markObjectiveQuestionParams.getObjectiveInfo()) {
-            if (StringUtils.isBlank(answer.getAnswer())) {
-                throw ExceptionResultEnum.ERROR.exception("答案不能为空");
-            }
-            if (answer.getObjectivePolicy() != null && answer.getObjectivePolicy().equals(ObjectivePolicy.LEAK)) {
-                if (answer.getObjectivePolicyScore() == null) {
-                    throw ExceptionResultEnum.ERROR.exception("漏选给分不能没有计分规则");
-                }
-                if (answer.getObjectivePolicyScore() > (answer.getTotalScore() / answer.getAnswer().length())) {
-                    throw ExceptionResultEnum.ERROR.exception("漏选给分计分规则有误");
-                }
-            }
-            UpdateWrapper<MarkQuestionAnswer> updateWrapper = new UpdateWrapper<>();
-            updateWrapper.lambda().set(MarkQuestionAnswer::getAnswer, answer.getAnswer())
-                    .set(MarkQuestionAnswer::getUpdateTime, System.currentTimeMillis());
-            if (answer.getObjectivePolicy() != null) {
-                updateWrapper.lambda().set(MarkQuestionAnswer::getObjectivePolicy, answer.getObjectivePolicy());
-            }
-            if (answer.getObjectivePolicyScore() != null) {
-                updateWrapper.lambda().set(MarkQuestionAnswer::getObjectivePolicyScore, answer.getObjectivePolicyScore());
-            }
-            updateWrapper.lambda().eq(MarkQuestionAnswer::getId, answer.getId());
-            markQuestionAnswerService.update(updateWrapper);
-        }
-
-        // 统分
-        markStudentService.calcObjectiveScore(markObjectiveQuestionParams.getExamId(), markObjectiveQuestionParams.getPaperNumber());
-    }
-
-    @Override
-    public String uploadSubjectiveFile(Long examId, String paperNumber, String paperType, MultipartFile file, String md5) {
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        if (markPaper == null) {
-            throw ExceptionResultEnum.ERROR.exception("课程不存在");
-        }
-        List<MarkPaperFileDto> subjectiveAnswerFileDtoList = new ArrayList<>();
-        if (StringUtils.isNotBlank(markPaper.getAnswerFilePath())) {
-            subjectiveAnswerFileDtoList = JSON.parseArray(markPaper.getAnswerFilePath(), MarkPaperFileDto.class);
-        }
-        String answerFilePath = fileStoreUtil.uploadFile(file, md5, UploadFileEnum.PDF);
-
-        MarkPaperFileDto subjectiveAnswerFileDto = null;
-        for (MarkPaperFileDto answerFileDto : subjectiveAnswerFileDtoList) {
-            if (paperType.equals(answerFileDto.getPaperType())) {
-                answerFileDto.setFilePathVo(JSON.parseObject(answerFilePath, FilePathVo.class));
-                subjectiveAnswerFileDto = answerFileDto;
-            }
-        }
-
-        if (subjectiveAnswerFileDto == null) {
-            subjectiveAnswerFileDto = new MarkPaperFileDto();
-            subjectiveAnswerFileDto.setPaperType(paperType);
-            subjectiveAnswerFileDto.setFilePathVo(JSON.parseObject(answerFilePath, FilePathVo.class));
-            subjectiveAnswerFileDtoList.add(subjectiveAnswerFileDto);
-        }
-
-        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkPaper::getAnswerFilePath, JSON.toJSONString(subjectiveAnswerFileDtoList))
-                .eq(MarkPaper::getExamId, examId)
-                .eq(MarkPaper::getPaperNumber, paperNumber);
-        markPaperService.update(updateWrapper);
-        return teachcloudCommonService.filePreview(answerFilePath);
-    }
-
-    @Override
-    public List<MarkPaperFileDto> previewAnswerFileByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        List<MarkPaperFileDto> markPaperFileDtos = new ArrayList<>();
-        if (markPaper != null && StringUtils.isNotBlank(markPaper.getAnswerFilePath())) {
-            markPaperFileDtos = JSON.parseArray(markPaper.getAnswerFilePath(), MarkPaperFileDto.class);
-            for (MarkPaperFileDto markPaperFileDto : markPaperFileDtos) {
-                markPaperFileDto.setUrl(teachcloudCommonService.filePreview(JSON.toJSONString(markPaperFileDto.getFilePathVo())));
-                markPaperFileDto.setFilePathVo(null);
-            }
-        }
-        return markPaperFileDtos;
-    }
-
-    @Override
-    public List<MarkPaperFileDto> previewPaperFileByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        List<MarkPaperFileDto> markPaperFileDtos = new ArrayList<>();
-        if (markPaper != null && StringUtils.isNotBlank(markPaper.getPaperFilePath())) {
-            markPaperFileDtos = JSON.parseArray(markPaper.getPaperFilePath(), MarkPaperFileDto.class);
-            for (MarkPaperFileDto markPaperFileDto : markPaperFileDtos) {
-                markPaperFileDto.setUrl(teachcloudCommonService.filePreview(JSON.toJSONString(markPaperFileDto.getFilePathVo())));
-                markPaperFileDto.setFilePathVo(null);
-            }
-        }
-        return markPaperFileDtos;
-    }
-
-    @Override
-    public List<MarkQuestion> listByExamIdAndPaperNumberAndPaperIndexAndPageIndex(Long examId, String paperNumber,
-                                                                                  Integer paperIndex, Integer pageIndex, boolean isObjective) {
-        QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkQuestion::getExamId, examId)
-                .eq(MarkQuestion::getPaperNumber, paperNumber)
-                .eq(MarkQuestion::getPaperIndex, paperIndex)
-                .eq(MarkQuestion::getPageIndex, pageIndex)
-                .eq(MarkQuestion::getObjective, isObjective)
-                .orderByAsc(MarkQuestion::getMainNumber)
-                .orderByAsc(MarkQuestion::getSubNumber);
-        return this.list(queryWrapper);
-    }
-
-    @Override
-    public MarkQuestionDto pageQuestionsByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        MarkQuestionDto markQuestionDto = new MarkQuestionDto();
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        // 试卷结构,默认只取A卷
-        List<MarkQuestion> markQuestionList = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber);
-        markQuestionDto.setQuestionSubmit(markPaper.getQuestionStatus());
-        markQuestionDto.setObjectiveSubmit(markQuestionAnswerService.countByExamIdAndPaperNumber(examId, paperNumber) > 0);
-        markQuestionDto.setQuestions(markQuestionList);
-        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<>();
-        LambdaQueryWrapper<MarkQuestion> lambdaQueryWrapper = queryWrapper.lambda();
-        lambdaQueryWrapper.eq(MarkQuestion::getExamId, examId)
-                .eq(MarkQuestion::getPaperNumber, paperNumber)
-                .eq(MarkQuestion::getObjective, objective);
-        lambdaQueryWrapper.orderByAsc(MarkQuestion::getMainNumber)
-                .orderByAsc(MarkQuestion::getSubNumber);
-        return this.list(queryWrapper);
-    }
-
-    @Override
-    public void deleteByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().eq(MarkQuestion::getExamId, examId)
-                .eq(MarkQuestion::getPaperNumber, paperNumber);
-        this.remove(updateWrapper);
-    }
-
-    @Transactional
-    @Override
-    public void updateMarkPaperScore(Long examId, String paperNumber) {
-        List<MarkQuestion> markQuestions = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber);
-        Double objectiveScore = markQuestions.stream().filter(MarkQuestion::getObjective)
-                .mapToDouble(MarkQuestion::getTotalScore).sum();
-        Double subjectiveScore = markQuestions.stream().filter(m -> !m.getObjective())
-                .mapToDouble(MarkQuestion::getTotalScore).sum();
-        Double totalScore = markQuestions.stream().mapToDouble(MarkQuestion::getTotalScore).sum();
-        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkPaper::getObjectiveScore, objectiveScore)
-                .set(MarkPaper::getSubjectiveScore, subjectiveScore)
-                .set(MarkPaper::getTotalScore, totalScore)
-                .eq(MarkPaper::getExamId, examId)
-                .eq(MarkPaper::getPaperNumber, paperNumber);
-        markPaperService.update(updateWrapper);
-    }
-
-    @Override
-    public List<MarkQuestionAnswerVo> listQuestionAnswerByExamIdAndPaperNumberAndPaperType(Long examId, String paperNumber, String paperType, Boolean objective) {
-        return this.baseMapper.listQuestionAnswerByExamIdAndPaperNumberAndPaperType(examId, paperNumber, paperType, objective);
-    }
-
-    @Override
-    public List<MarkQuestion> listByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkQuestion::getExamId, examId)
-                .eq(MarkQuestion::getPaperNumber, paperNumber);
-        return this.list(queryWrapper);
-    }
-
-    @Override
-    public void deleteByExamIdAndPaperNumberAndObjective(Long examId, String paperNumber, boolean objective) {
-        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().eq(MarkQuestion::getExamId, examId)
-                .eq(MarkQuestion::getPaperNumber, paperNumber)
-                .eq(MarkQuestion::getObjective, objective);
-        this.remove(updateWrapper);
-    }
-
-    @Override
-    public MarkGroupTaskDto listGroupTaskByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        MarkGroupTaskDto markGroupTaskDto = new MarkGroupTaskDto();
-        markGroupTaskDto.setExamId(examId);
-        markGroupTaskDto.setPaperNumber(paperNumber);
-
-        // 主观题信息
-        List<MarkQuestion> markQuestionList = listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
-        for (MarkQuestion m : markQuestionList) {
-            if (StringUtils.isNotBlank(m.getPicList())) {
-                m.setPictureConfigs(JSON.parseArray(m.getPicList(), PictureConfig.class));
-            }
-            // 评卷员
-            List<MarkUser> markUserList = markUserQuestionService.listGroupUserByExamIdAndPaperNumberAndQuestionIdAndClassName(examId, paperNumber, m.getId(), null);
-            m.setMarkers(markUserList.stream().filter(MarkUser::getEnable).collect(Collectors.toList()));
-        }
-        markGroupTaskDto.setQuestions(markQuestionList);
-        // 分班阅参数
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        markGroupTaskDto.setClassMark(markPaper != null && markPaper.getClassMark());
-        markGroupTaskDto.setDoubleMark(markPaper != null && markPaper.getDoubleMark());
-        markGroupTaskDto.setMergeMarker(markPaper != null && markPaper.getMergeMarker());
-        return markGroupTaskDto;
-    }
-
-    @Override
-    public void updatePicListByQuestionId(QuestionPictureConfigParams questionPictureConfigParams) {
-        List<PictureConfig> pictureConfigs = questionPictureConfigParams.getPictureConfigs();
-        if (CollectionUtils.isEmpty(pictureConfigs)) {
-            throw ExceptionResultEnum.ERROR.exception("没有评卷区数据");
-        }
-        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkQuestion::getPicList, JSON.toJSONString(pictureConfigs))
-                .in(MarkQuestion::getId, questionPictureConfigParams.getQuestionIds());
-        this.update(updateWrapper);
-    }
-
-    @Override
-    public Integer countCurrentCountByExamIdAndPaperNumber(Long examId, String paperNumber) {
-        int total = 0;
-        List<MarkQuestion> markQuestionList = this.listByExamIdAndPaperNumber(examId, paperNumber);
-        if (CollectionUtils.isNotEmpty(markQuestionList)) {
-            for (MarkQuestion markQuestion : markQuestionList) {
-                total += markService.applyCurrentCount(markQuestion);
-            }
-        }
-        return total;
-    }
-
-    @Override
-    public MarkGroupSummaryProgressDto summaryGroupProgress(Long examId, String paperNumber) {
-        MarkGroupSummaryProgressDto markGroupSummaryProgressDto = new MarkGroupSummaryProgressDto();
-
-        // totalInfo
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        if (markPaper == null) {
-            return markGroupSummaryProgressDto;
-        }
-        markGroupSummaryProgressDto.setClassMark(markPaper.getClassMark());
-        MarkGroupTotalProgressDto markGroupTotalProgressDto = new MarkGroupTotalProgressDto();
-        markGroupTotalProgressDto.setStudentCount(markPaper.getStudentCount());
-        markGroupTotalProgressDto.setUploadCount(markStudentService.countUploadedByExamIdAndPaperNumber(examId, paperNumber));
-        markGroupTotalProgressDto.setAbsentCount(markStudentService.countAbsentByExamIdAndPaperNumber(examId, paperNumber));
-
-        int totalCount = markTaskService.countByExamIdAndPaperNumber(examId, paperNumber);
-        int markedCount = markTaskService.countByExamIdAndPaperNumberAndStatusIn(examId, paperNumber, Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED));
-        markGroupTotalProgressDto.setPercent(Calculator.divide2String(Calculator.multiply(markedCount, 100), Double.valueOf(totalCount), 2));
-        markGroupSummaryProgressDto.setTotalInfo(markGroupTotalProgressDto);
-
-        // groupInfo
-        List<MarkGroupProgressDto> markGroupProgressDtoList = new ArrayList<>();
-        List<MarkQuestion> markQuestionList = this.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
-        for (MarkQuestion markQuestion : markQuestionList) {
-            MarkGroupProgressDto markGroupProgressDto = new MarkGroupProgressDto();
-            markGroupProgressDto.setQuestionId(markQuestion.getId());
-            markGroupProgressDto.setTotalScore(markQuestion.getTotalScore());
-            markGroupProgressDto.setQuestionNumber(markQuestion.getQuestionNumber());
-            List<MarkUser> markUserList = markUserQuestionService.listGroupUserByExamIdAndPaperNumberAndQuestionIdAndClassName(examId, paperNumber, markQuestion.getId(), null);
-            markGroupProgressDto.setMarkerCount(markUserList.size());
-            markGroupProgressDto.setTaskCount(markQuestion.getTaskCount());
-            markGroupProgressDto.setMarkedCount(markQuestion.getMarkedCount());
-            markGroupProgressDto.setLeftCount(markGroupProgressDto.getTaskCount() - markGroupProgressDto.getMarkedCount());
-            markGroupProgressDto.setCurrentCount(markService.applyCurrentCount(markQuestion));
-            markGroupProgressDto.setPercent(Calculator.divide2String(Calculator.multiply(markGroupProgressDto.getMarkedCount(), 100), Double.valueOf(markGroupProgressDto.getTaskCount()), 2));
-            markGroupProgressDto.setArbitrateCount(markArbitrateHistoryService.waitArbitrateCount(examId, paperNumber, markQuestion.getId(), null));
-            markGroupProgressDtoList.add(markGroupProgressDto);
-        }
-        markGroupSummaryProgressDto.setGroupInfo(markGroupProgressDtoList);
-        return markGroupSummaryProgressDto;
-    }
-
-    @Override
-    public IPage<MarkGroupClassProgressDto> summaryGroupClassProgress(Long examId, String paperNumber, String className, Integer pageNumber, Integer pageSize) {
-        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-        if (markPaper.getClassMark()) {
-            Page<MarkGroupClassProgressDto> page = new Page<>(pageNumber, pageSize);
-            IPage<MarkGroupClassProgressDto> markGroupClassProgressDtoIPage = markUserClassService.pageClassByExamIdAndPaperNumber(page, examId, paperNumber, className);
-            for (MarkGroupClassProgressDto markGroupClassProgressDto : markGroupClassProgressDtoIPage.getRecords()) {
-                Long questionId = markGroupClassProgressDto.getQuestionId();
-                List<MarkTask> totalMarkTaskList = new ArrayList<>();
-                List<MarkUser> totalMarkUserList = new ArrayList<>();
-                totalMarkTaskList.addAll(markTaskService.listByExamIdAndPaperNumberAndQuestionIdAndUserIdAndClassName(examId, paperNumber, questionId, null, markGroupClassProgressDto.getClassName()));
-                totalMarkUserList.addAll(markUserQuestionService.listGroupUserByExamIdAndPaperNumberAndQuestionIdAndClassName(examId, paperNumber, questionId, markGroupClassProgressDto.getClassName()));
-
-                MarkQuestion markQuestion = this.getById(questionId);
-                List<MarkUser> markUserList = markUserClassService.listClassMarkerByExamIdAndPaperNumberAndQuestionIdAndClassName(examId, paperNumber, questionId, markGroupClassProgressDto.getClassName());
-                int count = 0;
-                for (MarkUser markUser : markUserList) {
-                    MarkUserQuestion markUserQuestion = markUserQuestionService.getByExamIdAndPaperNumberAndQuestionIdAndUserId(examId, paperNumber, questionId, markUser.getUserId());
-                    if (markUserQuestion != null) {
-                        int markerCount = markService.applyCurrentCount(markQuestion, markUserQuestion.getId());
-                        count += markerCount;
-                    }
-                }
-
-                // 待仲裁数量
-                int arbitrateCount = markArbitrateHistoryService.waitArbitrateCount(examId, paperNumber, questionId, markGroupClassProgressDto.getClassName());
-                markGroupClassProgressDto.setQuestionNumber(markQuestion.getQuestionNumber());
-                markGroupClassProgressDto.setMarkerCount(totalMarkUserList.size());
-                markGroupClassProgressDto.setTaskCount(totalMarkTaskList.size());
-                markGroupClassProgressDto.setMarkedCount(markTaskService.markedCount(totalMarkTaskList));
-                markGroupClassProgressDto.setLeftCount(markGroupClassProgressDto.getTaskCount() - markGroupClassProgressDto.getMarkedCount());
-                markGroupClassProgressDto.setCurrentCount(count);
-                markGroupClassProgressDto.setPercent(Calculator.divide2String(Calculator.multiply(markGroupClassProgressDto.getMarkedCount(), 100), Double.valueOf(markGroupClassProgressDto.getTaskCount()), 2));
-                markGroupClassProgressDto.setArbitrateCount(arbitrateCount);
-            }
-            return markGroupClassProgressDtoIPage;
-        }
-        return null;
-    }
-
-    @Override
-    public List<MarkGroupQuestionsDto> listGroupQuestions(Long examId, String paperNumber) {
-        List<MarkQuestion> markQuestionList = this.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
-        List<MarkGroupQuestionsDto> markGroupQuestionsDtoList = new ArrayList<>();
-        for (MarkQuestion markQuestion : markQuestionList) {
-            MarkGroupQuestionsDto markGroupQuestionsDto = new MarkGroupQuestionsDto();
-            markGroupQuestionsDto.setQuestionId(markQuestion.getId());
-            markGroupQuestionsDto.setQuestionNumber(markQuestion.getQuestionNumber());
-            markGroupQuestionsDtoList.add(markGroupQuestionsDto);
-        }
-        return markGroupQuestionsDtoList;
-    }
-
-    @Override
-    public void updateTaskCount(Long questionId, int taskCount) {
-        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkQuestion::getTaskCount, taskCount)
-                .eq(MarkQuestion::getId, questionId);
-        this.update(updateWrapper);
-    }
-
-    @Override
-    public void updateMarkedCount(Long questionId, int count) {
-        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkQuestion::getMarkedCount, count)
-                .eq(MarkQuestion::getId, questionId);
-        this.update(updateWrapper);
-    }
-
-    @Override
-    public void updateDoubleMarkByQuestionId(DoubleMarkParam doubleMarkParam) {
-        Long examId = doubleMarkParam.getExamId();
-        String paperNumber = doubleMarkParam.getPaperNumber();
-        Long questionId = doubleMarkParam.getQuestionId();
-        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);
-
-        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.lambda().set(MarkQuestion::getDoubleRate, doubleMarkParam.getDoubleRate())
-                .set(MarkQuestion::getArbitrateThreshold, doubleMarkParam.getArbitrateThreshold())
-                .set(MarkQuestion::getScorePolicy, doubleMarkParam.getScorePolicy())
-                .eq(MarkQuestion::getId, doubleMarkParam.getQuestionId());
-        this.update(updateWrapper);
-
-        // 单、又评切换、双评比例修改,删除任务
-        if (!markQuestion.getDoubleRate().equals(doubleMarkParam.getDoubleRate())) {
-            this.updateMarkedCount(questionId, 0);
-            this.updateTaskCount(questionId, 0);
-            if (lockService.trylock(LockType.QUESTION_UPDATE, questionId)) {
-                markSyncService.deleteMarkTask(markQuestion);
-            }
-        }
-
-
-    }
-
-    @Override
-    public MarkQuestionSubjectiveStepStatusDto stepStatus(Long examId, String paperNumber) {
-        List<MarkQuestion> markQuestionList = this.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
-        Boolean subjectiveStruct = false, subjectiveMarker = false, subjectiveMarkerClass = false, objectiveAnswer = false;
-        if (CollectionUtils.isNotEmpty(markQuestionList)) {
-            // 主观题结构,小题分是否设置
-            long countStruct = markQuestionList.stream().filter(m -> m.getTotalScore() == null || m.getIntervalScore() == null).count();
-            subjectiveStruct = countStruct == 0;
-
-            if (subjectiveStruct) {
-                // 主观题设置评卷员
-                long countPic = markQuestionList.stream().filter(m -> StringUtils.isBlank(m.getPicList())).count();
-                subjectiveMarker = countPic == 0;
-                if (subjectiveMarker) {
-                    for (MarkQuestion markQuestion : markQuestionList) {
-                        List<MarkUserQuestion> markUserQuestionList = markUserQuestionService.listByExamIdAndPaperNumberAndQuestionId(examId, paperNumber, markQuestion.getId());
-                        if (CollectionUtils.isEmpty(markUserQuestionList)) {
-                            subjectiveMarker = false;
-                            break;
-                        }
-                    }
-                }
-
-                if (subjectiveMarker) {
-                    // 分班阅
-                    MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
-                    subjectiveMarkerClass = markPaper != null && markPaper.getClassMark();
-                    if (subjectiveMarkerClass) {
-                        MarkStudentQuery markStudentQuery = new MarkStudentQuery();
-                        markStudentQuery.setExamId(examId);
-                        markStudentQuery.setPaperNumber(paperNumber);
-                        List<MarkStudentVo> markStudentList = markStudentService.listMarkStudentVo(markStudentQuery);
-                        List<String> teachClasses = markStudentList.stream().filter(m -> StringUtils.isNotBlank(m.getTeachClassName())).map(MarkStudentVo::getTeachClassName).distinct().collect(Collectors.toList());
-                        for (MarkQuestion markQuestion : markQuestionList) {
-                            List<String> classNames = markUserQuestionService.countClassByExamIdAndPaperNumberAndQuestionId(examId, paperNumber, markQuestion.getId());
-                            if (!CollectionUtils.isEqualCollection(teachClasses, classNames)) {
-                                subjectiveMarkerClass = false;
-                                break;
-                            }
-                        }
-                    }
-
-                    if (subjectiveMarkerClass) {
-                        // 客观题标答
-                        List<MarkQuestionAnswer> markQuestionAnswerList = markQuestionAnswerService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, null);
-                        long countAnswer = markQuestionAnswerList.stream().filter(m -> StringUtils.isBlank(m.getAnswer()) || m.getObjectivePolicy() == null).count();
-                        objectiveAnswer = countAnswer == 0;
-                    }
-                }
-            }
-        }
-
-        return new MarkQuestionSubjectiveStepStatusDto(subjectiveStruct, subjectiveMarker, subjectiveMarkerClass, objectiveAnswer);
-    }
-
-    @Override
-    public void updatePic(String content, List<MarkQuestion> markQuestions) {
-        List<SubPictureConfig> pictureConfigs = CardParseUtils.parseCardPic(content, markQuestions);
-        List<SubPictureConfig> subPictureConfigList;
-        for (MarkQuestion markQuestion : markQuestions) {
-            // 填空题区域为一个整体
-            if (markQuestion.getQuestionType() == 4) {
-                subPictureConfigList = pictureConfigs.stream().filter(m -> m.getMainNumber().equals(markQuestion.getMainNumber())).collect(Collectors.toList());
-            } else {
-                subPictureConfigList = pictureConfigs.stream().filter(m -> m.getQuestionNumber().equals(markQuestion.getQuestionNumber())).collect(Collectors.toList());
-            }
-            markQuestion.setPicList(SubPictureConfig.transfer(subPictureConfigList));
-        }
-        this.updateBatchById(markQuestions);
-    }
-
-    @Override
-    public long countUnBindMarkerByExamIdAndPaperNumberAndObjective(Long examId, String paperNumber, boolean objective) {
-        return this.baseMapper.countUnBindMarkerByExamIdAndPaperNumberAndObjective(examId, paperNumber, objective);
-    }
-
-    @Override
-    public long countByExamIdAndPaperNumberAndObjective(Long examId, String paperNumber, boolean objective) {
-        QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(MarkQuestion::getExamId, examId)
-                .eq(MarkQuestion::getPaperNumber, paperNumber)
-                .eq(MarkQuestion::getObjective, objective);
-        return this.count(queryWrapper);
-    }
-}
+package com.qmth.teachcloud.mark.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.teachcloud.common.bean.dto.mark.MarkUser;
+import com.qmth.teachcloud.common.bean.dto.mark.ObjectiveAnswerDto;
+import com.qmth.teachcloud.common.bean.dto.mark.PictureConfig;
+import com.qmth.teachcloud.common.bean.params.mark.group.QuestionPictureConfigParams;
+import com.qmth.teachcloud.common.bean.result.ExcelResult;
+import com.qmth.teachcloud.common.bean.vo.FilePathVo;
+import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.entity.MarkQuestion;
+import com.qmth.teachcloud.common.entity.SysUser;
+import com.qmth.teachcloud.common.enums.*;
+import com.qmth.teachcloud.common.service.TeachcloudCommonService;
+import com.qmth.teachcloud.common.util.ConvertUtil;
+import com.qmth.teachcloud.common.util.ExcelUtil;
+import com.qmth.teachcloud.common.util.FileStoreUtil;
+import com.qmth.teachcloud.common.util.ServletUtil;
+import com.qmth.teachcloud.mark.bean.mark.DoubleMarkParam;
+import com.qmth.teachcloud.mark.bean.student.MarkStudentQuery;
+import com.qmth.teachcloud.mark.bean.vo.parseCard.pic.SubPictureConfig;
+import com.qmth.teachcloud.mark.dto.mark.MarkQuestionAnswerVo;
+import com.qmth.teachcloud.mark.dto.mark.MarkStudentVo;
+import com.qmth.teachcloud.mark.dto.mark.MarkPaperFileDto;
+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.enums.LockType;
+import com.qmth.teachcloud.mark.enums.MarkTaskStatus;
+import com.qmth.teachcloud.mark.lock.LockService;
+import com.qmth.teachcloud.mark.mapper.MarkQuestionMapper;
+import com.qmth.teachcloud.mark.params.MarkObjectiveQuestionParams;
+import com.qmth.teachcloud.mark.params.MarkQuestionParams;
+import com.qmth.teachcloud.mark.service.*;
+import com.qmth.teachcloud.mark.utils.Calculator;
+import com.qmth.teachcloud.mark.utils.CardParseUtils;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * <p>
+ * 小题信息表 服务实现类
+ * </p>
+ *
+ * @author xf
+ * @since 2023-09-22
+ */
+@Service
+public class MarkQuestionServiceImpl extends ServiceImpl<MarkQuestionMapper, MarkQuestion> implements MarkQuestionService {
+
+    @Resource
+    private TeachcloudCommonService teachcloudCommonService;
+    @Resource
+    private MarkPaperService markPaperService;
+    @Resource
+    private MarkQuestionAnswerService markQuestionAnswerService;
+    @Resource
+    private MarkUserQuestionService markUserQuestionService;
+    @Resource
+    private MarkUserClassService markUserClassService;
+    @Resource
+    private MarkStudentService markStudentService;
+    @Resource
+    private MarkTaskService markTaskService;
+    @Resource
+    private MarkArbitrateHistoryService markArbitrateHistoryService;
+    @Resource
+    private MarkService markService;
+    @Resource
+    private FileStoreUtil fileStoreUtil;
+    @Resource
+    private LockService lockService;
+    @Resource
+    private MarkSyncService markSyncService;
+
+    @Override
+    public String assembleGroupQuestionsByExamIdAndPaperNumberAndNumber(Long examId, String paperNumber, Integer groupNumber) {
+        return this.baseMapper.assembleQuestionsByExamIdAndPaperNumberAndNumber(examId, paperNumber, groupNumber);
+    }
+
+    @Override
+    public List<MarkQuestion> listQuestionByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
+        LambdaQueryWrapper<MarkQuestion> lambdaQueryWrapper = queryWrapper.lambda();
+        lambdaQueryWrapper.eq(MarkQuestion::getExamId, examId)
+                .eq(MarkQuestion::getPaperNumber, paperNumber);
+        lambdaQueryWrapper.orderByAsc(MarkQuestion::getMainNumber)
+                .orderByAsc(MarkQuestion::getSubNumber);
+        return this.list(queryWrapper);
+    }
+
+    @Transactional
+    @Override
+    public void saveQuestions(MarkQuestionParams markQuestionParams) {
+        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
+        Long examId = markQuestionParams.getExamId();
+        String paperNumber = markQuestionParams.getPaperNumber();
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+
+        List<MarkQuestion> questions = markQuestionParams.getQuestions();
+        if (CollectionUtils.isEmpty(questions)) {
+            throw ExceptionResultEnum.ERROR.exception("没有可保存的结构数据");
+        }
+
+        // 校验题号不能重复
+        Map<String, Long> longMap = questions.stream().collect(Collectors.groupingBy(m -> m.getMainNumber() + SystemConstant.HYPHEN + m.getSubNumber(), Collectors.counting()));
+        long count = longMap.entrySet().stream().filter(m -> m.getValue().intValue() > 1).count();
+        if (count > 0) {
+            throw ExceptionResultEnum.ERROR.exception("大题号-小题号有重复数据");
+        }
+
+        // 查询已有结构信息
+        List<MarkQuestion> markQuestionList = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber);
+        if (CollectionUtils.isEmpty(markQuestionList)) {
+            for (MarkQuestion question : questions) {
+                if (question.getTotalScore() < 0) {
+                    throw ExceptionResultEnum.ERROR.exception("小题满分不能小于0");
+                }
+                if (!question.getObjective() && (question.getIntervalScore() == null || question.getIntervalScore() <= 0)) {
+                    throw ExceptionResultEnum.ERROR.exception("间隔分必须大于0");
+                }
+                question.setExamId(examId);
+                question.setPaperNumber(paperNumber);
+                question.setPaperType(markPaper.getPaperType());
+                question.setCreateId(sysUser.getId());
+                question.setCreateTime(System.currentTimeMillis());
+                // 用来判断是否保存过
+                question.setUpdateId(sysUser.getId());
+                question.setUpdateTime(System.currentTimeMillis());
+            }
+            this.saveBatch(questions);
+            markQuestionAnswerService.deleteByExamIdAndPaperNumber(examId, paperNumber);
+            markQuestionAnswerService.saveByMarkQuestion(examId, paperNumber, questions, false);
+        } else {
+            List<MarkQuestion> saveOrUpdateList = new ArrayList<>();
+            for (MarkQuestion question : questions) {
+                if (Objects.isNull(question.getId())) {
+                    question.setId(SystemConstant.getDbUuid());
+                    question.setExamId(examId);
+                    question.setPaperNumber(paperNumber);
+                    question.setPaperType(markPaper.getPaperType());
+                    saveOrUpdateList.add(question);
+                } else {
+                    Optional<MarkQuestion> questionOptional = markQuestionList.stream().filter(m -> m.getId().equals(question.getId())).findFirst();
+                    if (questionOptional.isPresent()) {
+                        MarkQuestion markQuestion = questionOptional.get();
+                        if (markQuestion.getMarkedCount() != null && markQuestion.getMarkedCount() > 0 && markQuestion.getTotalScore() != null && markQuestion.getTotalScore().doubleValue() - question.getTotalScore().doubleValue() != 0) {
+                            throw ExceptionResultEnum.ERROR.exception("大题号" + markQuestion.getMainNumber() + "小题号" + markQuestion.getSubNumber() + "已阅卷,不能修改小题满分");
+                        }
+                        // 主观题改客观题
+                        if (markQuestion.getMarkedCount() != null && markQuestion.getMarkedCount() > 0 && !markQuestion.getObjective() && question.getObjective()) {
+                            throw ExceptionResultEnum.ERROR.exception("大题号" + markQuestion.getMainNumber() + "小题号" + markQuestion.getSubNumber() + "已阅卷,不能修改为客观题题型");
+                        }
+                        if (question.getTotalScore() < 0) {
+                            throw ExceptionResultEnum.ERROR.exception("小题满分不能小于0");
+                        }
+                        if (!QuestionType.findByValue(question.getQuestionType()).getObjective() && question.getIntervalScore() <= 0) {
+                            throw ExceptionResultEnum.ERROR.exception("间隔分必须大于0");
+                        }
+                        // 客观题变主观题
+                        if (markQuestion.getObjective() && !question.getObjective()) {
+                            // 清除判分策略
+                            markQuestionAnswerService.deleteByExamIdAndPaperNumberAndMainNumberAndSubNumber(examId, paperNumber, markQuestion.getMainNumber(), markQuestion.getSubNumber());
+                        }
+                        // 客观题
+                        if (question.getObjective()) {
+                            markQuestion.setOptionCount(question.getOptionCount());
+                            if (!markQuestion.getQuestionType().equals(question.getQuestionType())) {
+                                // 清除判分策略
+                                markQuestionAnswerService.resetObjectivePolicyByExamIdAndPaperNumberAndMainNumberAndSubNumber(examId, paperNumber, markQuestion.getMainNumber(), markQuestion.getSubNumber());
+                            }
+                        }
+                        // 大题号
+                        markQuestion.setMainNumber(question.getMainNumber());
+                        // 小题号
+                        markQuestion.setSubNumber(question.getSubNumber());
+                        // 大题名称
+                        markQuestion.setMainTitle(question.getMainTitle());
+                        // 题型
+                        markQuestion.setQuestionType(question.getQuestionType());
+                        markQuestion.setObjective(question.getObjective());
+                        markQuestion.setTotalScore(question.getTotalScore());
+                        markQuestion.setIntervalScore(question.getIntervalScore());
+                        // 用来判断是否保存过
+                        markQuestion.setUpdateId(sysUser.getId());
+                        markQuestion.setUpdateTime(System.currentTimeMillis());
+                        saveOrUpdateList.add(markQuestion);
+                        markQuestionList.remove(markQuestion);
+                    } else {
+                        question.setId(SystemConstant.getDbUuid());
+                        question.setExamId(examId);
+                        question.setPaperNumber(paperNumber);
+                        question.setPaperType(markPaper.getPaperType());
+                        question.setCreateId(sysUser.getId());
+                        question.setCreateTime(System.currentTimeMillis());
+                        // 用来判断是否保存过
+                        question.setUpdateId(sysUser.getId());
+                        question.setUpdateTime(System.currentTimeMillis());
+                        saveOrUpdateList.add(question);
+                    }
+                }
+            }
+            // 删除的结构
+            if (CollectionUtils.isNotEmpty(markQuestionList)) {
+                List<Long> deleteIds = markQuestionList.stream().map(MarkQuestion::getId).collect(Collectors.toList());
+                this.removeByIds(deleteIds);
+
+                markQuestionList.stream().filter(m -> m.getObjective()).forEach(m -> {
+                    markQuestionAnswerService.deleteByExamIdAndPaperNumberAndMainNumberAndSubNumber(examId, paperNumber, m.getMainNumber(), m.getSubNumber());
+                });
+            }
+            this.saveOrUpdateBatch(saveOrUpdateList);
+            markQuestionAnswerService.saveByMarkQuestion(examId, paperNumber, saveOrUpdateList, false);
+        }
+        // 更新客观题满分、主观题满分、总分
+        List<MarkQuestion> markQuestions = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber);
+        Double objectiveScore = markQuestions.stream().filter(m -> m.getObjective()).collect(Collectors.summingDouble(m -> m.getTotalScore()));
+        Double subjectiveScore = markQuestions.stream().filter(m -> !m.getObjective()).collect(Collectors.summingDouble(m -> m.getTotalScore()));
+        Double totalScore = markQuestions.stream().collect(Collectors.summingDouble(m -> m.getTotalScore()));
+        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkPaper::getObjectiveScore, objectiveScore)
+                .set(MarkPaper::getSubjectiveScore, subjectiveScore)
+                .set(MarkPaper::getTotalScore, totalScore)
+                .set(MarkPaper::getQuestionStatus, true)
+                .eq(MarkPaper::getExamId, examId)
+                .eq(MarkPaper::getPaperNumber, paperNumber);
+        markPaperService.update(updateWrapper);
+
+        // 主观题结构有变动
+        List<MarkQuestion> subjectiveQuestions = questions.stream().filter(m -> m.getObjective()).collect(Collectors.toList());
+        List<MarkQuestion> subjectiveMarkQuestionList = markQuestionList.stream().filter(m -> m.getObjective()).collect(Collectors.toList());
+        if (!CollectionUtils.isEqualCollection(subjectiveQuestions, subjectiveMarkQuestionList)) {
+            // 未分组的题目
+//            long unGroupQuestionCount = this.countByExamIdAndPaperNumberAndObjectiveAndGroupNumberIsNull(examId, paperNumber, false);
+//            // 考生主观题重新统分
+//            markService.checkStudentSubjectiveScore(examId, markPaper.getCoursePaperId(), unGroupQuestionCount);
+        }
+
+        // 更新分组状态
+        markService.updateMarkGroupStatus(examId, paperNumber);
+    }
+
+    @Override
+    public void objectiveAnswerSettingModelExport(Long examId, String paperNumber, String paperType, HttpServletResponse response)
+            throws Exception {
+        List<MarkQuestionAnswerVo> datasource = this.listQuestionAnswerByExamIdAndPaperNumberAndPaperType(examId, paperNumber,
+                paperType, true);
+        List<ObjectiveAnswerDto> objectiveAnswerDtoList = datasource.stream().flatMap(e -> {
+            ObjectiveAnswerDto dto = new ObjectiveAnswerDto();
+            dto.setMainNumber(String.valueOf(e.getMainNumber()));
+            dto.setSubNumber(String.valueOf(e.getSubNumber()));
+            dto.setMainTitle(e.getMainTitle());
+            dto.setAnswer(e.getAnswer());
+            return Stream.of(dto);
+        }).collect(Collectors.toList());
+
+        // 生成excel文件
+        ExcelUtil.excelExport("客观题标答模板", ObjectiveAnswerDto.class, objectiveAnswerDtoList, response);
+    }
+
+    @Transactional
+    @Override
+    public void objectiveAnswerSettingImport(Long examId, String paperNumber, String paperType, MultipartFile file) throws Exception {
+        // 试卷结构
+        List<MarkQuestionAnswer> datasource = markQuestionAnswerService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, paperType);
+        ExcelResult<ObjectiveAnswerDto> excelResult = ConvertUtil.analyzeExcel(file.getInputStream(), ObjectiveAnswerDto.class, false, 0);
+        if (!excelResult.isSuccess()) {
+            throw ExceptionResultEnum.ERROR.exception(excelResult.getErrorMsg());
+        }
+        // 逻辑异常
+        List<String> logicErrorList = new ArrayList<>();
+        List<ObjectiveAnswerDto> objectiveAnswerDtoList = excelResult.getDatasource();
+        if (!Objects.equals(datasource.size(), objectiveAnswerDtoList.size())) {
+            logicErrorList.add("试卷结构试题数量和导入文件中的试题数量不一致");
+        }
+        Set<String> questionNumberSet = new HashSet<>();
+        Map<String, MarkQuestion> markQuestionMap = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber).stream().collect(Collectors.toMap(k -> k.getMainNumber() + SystemConstant.HYPHEN + k.getSubNumber(), v -> v));
+        // 构建题号-试题结构map
+        Map<String, MarkQuestionAnswer> markQuestionAnswerMapMap = datasource.stream().collect(Collectors.toMap(k -> k.getMainNumber() + SystemConstant.HYPHEN + k.getSubNumber(), v -> v));
+
+        List<MarkQuestionAnswer> willEditList = new ArrayList<>();
+        for (int i = 0; i < objectiveAnswerDtoList.size(); i++) {
+            int rowIndex = i + 2;
+            ObjectiveAnswerDto cell = objectiveAnswerDtoList.get(i);
+            int exMainNumber = Integer.parseInt(cell.getMainNumber());
+            int exSubNumber = Integer.parseInt(cell.getSubNumber());
+            String exMainTitle = cell.getMainTitle();
+            String exAnswer = cell.getAnswer();
+
+            String key = exMainNumber + SystemConstant.HYPHEN + exSubNumber;
+            String unifyNotice = String.format("第[%s]行,大题号[%s],小题号[%s]的试题", rowIndex, exMainNumber, exSubNumber);
+            List<String> rowException = new ArrayList<>();
+            if (questionNumberSet.contains(key)) {
+                // 题号重复检验
+                rowException.add("题号和前面重复");
+            } else {
+                questionNumberSet.add(key);
+                if (markQuestionAnswerMapMap.containsKey(key)) {
+                    MarkQuestionAnswer markQuestion = markQuestionAnswerMapMap.get(key);
+                    markQuestion.setAnswer(exAnswer);
+                    willEditList.add(markQuestion);
+                    String mainTitle = markQuestion.getMainTitle();
+                    if (!Objects.equals(exMainTitle, mainTitle)) {
+                        rowException.add(String.format("大题名称[%s]错误", exMainTitle));
+                    } else {
+                        Integer questionType = markQuestionMap.get(key).getQuestionType();
+                        Integer optionCount = markQuestionMap.get(key).getOptionCount();
+                        List<String> optionScope = new ArrayList<>();
+                        try {
+                            for (int j = 1; j <= optionCount; j++) {
+                                String code = OptionsEnum.getCodeByIndex(j);
+                                optionScope.add(code);
+                            }
+                        } catch (Exception e) {
+                            throw ExceptionResultEnum.ERROR.exception(
+                                    String.format("第[%s]行,大题号[%s],小题号[%s]的试题答案可选选项解析失败", rowIndex, exMainNumber,
+                                            exSubNumber));
+                        }
+
+                        // 答案选项越界校验
+                        List<String> answerList = Arrays.asList(exAnswer.split(""));
+                        if (CollectionUtils.isNotEmpty(answerList)) {
+                            for (String answer : answerList) {
+                                String outOfBoundsException = String.format("答案[%s]错误。答案只能从[%s]中选择", answer, String.join("", optionScope));
+                                try {
+                                    OptionsEnum optionsEnum = OptionsEnum.getByCode(answer);
+                                    int optionIndex = optionsEnum.getIndex();
+                                    if (optionIndex > optionCount) {
+                                        // 答案选项超出范围
+                                        rowException.add(outOfBoundsException);
+                                    }
+                                } catch (Exception e) {
+                                    rowException.add(outOfBoundsException);
+                                }
+                            }
+                        } else {
+                            rowException.add("答案必填");
+                        }
+
+                        // 答案和题型匹配校验
+                        if (Objects.equals(questionType, 1)) {
+                            // 单选题
+                            if (answerList.size() > 1) {
+                                rowException.add(String.format("为[单选题],答案[%s]错误", exAnswer));
+                            }
+                        } else if (Objects.equals(questionType, 2)) {
+                            // 多选题
+
+                        } else if (Objects.equals(questionType, 3)) {
+                            // 判断题
+                            if (answerList.size() > 1) {
+                                rowException.add(String.format("为[判断题],答案[%s]错误", exAnswer));
+                            }
+                        } else {
+                            rowException.add("试题题型错误");
+                        }
+                    }
+
+                } else {
+                    rowException.add("题号错误,试卷中不存在该题号");
+                }
+            }
+            if (CollectionUtils.isNotEmpty(rowException)) {
+                unifyNotice = unifyNotice + String.join(",", rowException);
+                logicErrorList.add(unifyNotice);
+            }
+        }
+        if (CollectionUtils.isNotEmpty(logicErrorList)) {
+            throw ExceptionResultEnum.ERROR.exception(String.join(";\n", logicErrorList));
+        }
+        markQuestionAnswerService.saveOrUpdateBatch(willEditList);
+    }
+
+    @Transactional
+    @Override
+    public void saveObjectiveQuestions(MarkObjectiveQuestionParams markObjectiveQuestionParams) {
+        for (MarkQuestionAnswer answer : markObjectiveQuestionParams.getObjectiveInfo()) {
+            if (StringUtils.isBlank(answer.getAnswer())) {
+                throw ExceptionResultEnum.ERROR.exception("答案不能为空");
+            }
+            if (answer.getObjectivePolicy() != null && answer.getObjectivePolicy().equals(ObjectivePolicy.LEAK)) {
+                if (answer.getObjectivePolicyScore() == null) {
+                    throw ExceptionResultEnum.ERROR.exception("漏选给分不能没有计分规则");
+                }
+                if (answer.getObjectivePolicyScore() > (answer.getTotalScore() / answer.getAnswer().length())) {
+                    throw ExceptionResultEnum.ERROR.exception("漏选给分计分规则有误");
+                }
+            }
+            UpdateWrapper<MarkQuestionAnswer> updateWrapper = new UpdateWrapper<>();
+            updateWrapper.lambda().set(MarkQuestionAnswer::getAnswer, answer.getAnswer())
+                    .set(MarkQuestionAnswer::getUpdateTime, System.currentTimeMillis());
+            if (answer.getObjectivePolicy() != null) {
+                updateWrapper.lambda().set(MarkQuestionAnswer::getObjectivePolicy, answer.getObjectivePolicy());
+            }
+            if (answer.getObjectivePolicyScore() != null) {
+                updateWrapper.lambda().set(MarkQuestionAnswer::getObjectivePolicyScore, answer.getObjectivePolicyScore());
+            }
+            updateWrapper.lambda().eq(MarkQuestionAnswer::getId, answer.getId());
+            markQuestionAnswerService.update(updateWrapper);
+        }
+
+        // 统分
+        markStudentService.calcObjectiveScore(markObjectiveQuestionParams.getExamId(), markObjectiveQuestionParams.getPaperNumber());
+
+        //发送阅卷待办短信
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markObjectiveQuestionParams.getExamId(), markObjectiveQuestionParams.getPaperNumber());
+        Objects.requireNonNull(markPaper, "未找到试卷信息");
+        markUserQuestionService.smsMarkTask(markObjectiveQuestionParams.getExamId(), markPaper.getCourseId(), markObjectiveQuestionParams.getPaperNumber());
+    }
+
+    @Override
+    public String uploadSubjectiveFile(Long examId, String paperNumber, String paperType, MultipartFile file, String md5) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        if (markPaper == null) {
+            throw ExceptionResultEnum.ERROR.exception("课程不存在");
+        }
+        List<MarkPaperFileDto> subjectiveAnswerFileDtoList = new ArrayList<>();
+        if (StringUtils.isNotBlank(markPaper.getAnswerFilePath())) {
+            subjectiveAnswerFileDtoList = JSON.parseArray(markPaper.getAnswerFilePath(), MarkPaperFileDto.class);
+        }
+        String answerFilePath = fileStoreUtil.uploadFile(file, md5, UploadFileEnum.PDF);
+
+        MarkPaperFileDto subjectiveAnswerFileDto = null;
+        for (MarkPaperFileDto answerFileDto : subjectiveAnswerFileDtoList) {
+            if (paperType.equals(answerFileDto.getPaperType())) {
+                answerFileDto.setFilePathVo(JSON.parseObject(answerFilePath, FilePathVo.class));
+                subjectiveAnswerFileDto = answerFileDto;
+            }
+        }
+
+        if (subjectiveAnswerFileDto == null) {
+            subjectiveAnswerFileDto = new MarkPaperFileDto();
+            subjectiveAnswerFileDto.setPaperType(paperType);
+            subjectiveAnswerFileDto.setFilePathVo(JSON.parseObject(answerFilePath, FilePathVo.class));
+            subjectiveAnswerFileDtoList.add(subjectiveAnswerFileDto);
+        }
+
+        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkPaper::getAnswerFilePath, JSON.toJSONString(subjectiveAnswerFileDtoList))
+                .eq(MarkPaper::getExamId, examId)
+                .eq(MarkPaper::getPaperNumber, paperNumber);
+        markPaperService.update(updateWrapper);
+        return teachcloudCommonService.filePreview(answerFilePath);
+    }
+
+    @Override
+    public List<MarkPaperFileDto> previewAnswerFileByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        List<MarkPaperFileDto> markPaperFileDtos = new ArrayList<>();
+        if (markPaper != null && StringUtils.isNotBlank(markPaper.getAnswerFilePath())) {
+            markPaperFileDtos = JSON.parseArray(markPaper.getAnswerFilePath(), MarkPaperFileDto.class);
+            for (MarkPaperFileDto markPaperFileDto : markPaperFileDtos) {
+                markPaperFileDto.setUrl(teachcloudCommonService.filePreview(JSON.toJSONString(markPaperFileDto.getFilePathVo())));
+                markPaperFileDto.setFilePathVo(null);
+            }
+        }
+        return markPaperFileDtos;
+    }
+
+    @Override
+    public List<MarkPaperFileDto> previewPaperFileByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        List<MarkPaperFileDto> markPaperFileDtos = new ArrayList<>();
+        if (markPaper != null && StringUtils.isNotBlank(markPaper.getPaperFilePath())) {
+            markPaperFileDtos = JSON.parseArray(markPaper.getPaperFilePath(), MarkPaperFileDto.class);
+            for (MarkPaperFileDto markPaperFileDto : markPaperFileDtos) {
+                markPaperFileDto.setUrl(teachcloudCommonService.filePreview(JSON.toJSONString(markPaperFileDto.getFilePathVo())));
+                markPaperFileDto.setFilePathVo(null);
+            }
+        }
+        return markPaperFileDtos;
+    }
+
+    @Override
+    public List<MarkQuestion> listByExamIdAndPaperNumberAndPaperIndexAndPageIndex(Long examId, String paperNumber,
+                                                                                  Integer paperIndex, Integer pageIndex, boolean isObjective) {
+        QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkQuestion::getExamId, examId)
+                .eq(MarkQuestion::getPaperNumber, paperNumber)
+                .eq(MarkQuestion::getPaperIndex, paperIndex)
+                .eq(MarkQuestion::getPageIndex, pageIndex)
+                .eq(MarkQuestion::getObjective, isObjective)
+                .orderByAsc(MarkQuestion::getMainNumber)
+                .orderByAsc(MarkQuestion::getSubNumber);
+        return this.list(queryWrapper);
+    }
+
+    @Override
+    public MarkQuestionDto pageQuestionsByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        MarkQuestionDto markQuestionDto = new MarkQuestionDto();
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        // 试卷结构,默认只取A卷
+        List<MarkQuestion> markQuestionList = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber);
+        markQuestionDto.setQuestionSubmit(markPaper.getQuestionStatus());
+        markQuestionDto.setObjectiveSubmit(markQuestionAnswerService.countByExamIdAndPaperNumber(examId, paperNumber) > 0);
+        markQuestionDto.setQuestions(markQuestionList);
+        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<>();
+        LambdaQueryWrapper<MarkQuestion> lambdaQueryWrapper = queryWrapper.lambda();
+        lambdaQueryWrapper.eq(MarkQuestion::getExamId, examId)
+                .eq(MarkQuestion::getPaperNumber, paperNumber)
+                .eq(MarkQuestion::getObjective, objective);
+        lambdaQueryWrapper.orderByAsc(MarkQuestion::getMainNumber)
+                .orderByAsc(MarkQuestion::getSubNumber);
+        return this.list(queryWrapper);
+    }
+
+    @Override
+    public void deleteByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().eq(MarkQuestion::getExamId, examId)
+                .eq(MarkQuestion::getPaperNumber, paperNumber);
+        this.remove(updateWrapper);
+    }
+
+    @Transactional
+    @Override
+    public void updateMarkPaperScore(Long examId, String paperNumber) {
+        List<MarkQuestion> markQuestions = this.listQuestionByExamIdAndPaperNumber(examId, paperNumber);
+        Double objectiveScore = markQuestions.stream().filter(MarkQuestion::getObjective)
+                .mapToDouble(MarkQuestion::getTotalScore).sum();
+        Double subjectiveScore = markQuestions.stream().filter(m -> !m.getObjective())
+                .mapToDouble(MarkQuestion::getTotalScore).sum();
+        Double totalScore = markQuestions.stream().mapToDouble(MarkQuestion::getTotalScore).sum();
+        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkPaper::getObjectiveScore, objectiveScore)
+                .set(MarkPaper::getSubjectiveScore, subjectiveScore)
+                .set(MarkPaper::getTotalScore, totalScore)
+                .eq(MarkPaper::getExamId, examId)
+                .eq(MarkPaper::getPaperNumber, paperNumber);
+        markPaperService.update(updateWrapper);
+    }
+
+    @Override
+    public List<MarkQuestionAnswerVo> listQuestionAnswerByExamIdAndPaperNumberAndPaperType(Long examId, String paperNumber, String paperType, Boolean objective) {
+        return this.baseMapper.listQuestionAnswerByExamIdAndPaperNumberAndPaperType(examId, paperNumber, paperType, objective);
+    }
+
+    @Override
+    public List<MarkQuestion> listByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkQuestion::getExamId, examId)
+                .eq(MarkQuestion::getPaperNumber, paperNumber);
+        return this.list(queryWrapper);
+    }
+
+    @Override
+    public void deleteByExamIdAndPaperNumberAndObjective(Long examId, String paperNumber, boolean objective) {
+        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().eq(MarkQuestion::getExamId, examId)
+                .eq(MarkQuestion::getPaperNumber, paperNumber)
+                .eq(MarkQuestion::getObjective, objective);
+        this.remove(updateWrapper);
+    }
+
+    @Override
+    public MarkGroupTaskDto listGroupTaskByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        MarkGroupTaskDto markGroupTaskDto = new MarkGroupTaskDto();
+        markGroupTaskDto.setExamId(examId);
+        markGroupTaskDto.setPaperNumber(paperNumber);
+
+        // 主观题信息
+        List<MarkQuestion> markQuestionList = listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
+        for (MarkQuestion m : markQuestionList) {
+            if (StringUtils.isNotBlank(m.getPicList())) {
+                m.setPictureConfigs(JSON.parseArray(m.getPicList(), PictureConfig.class));
+            }
+            // 评卷员
+            List<MarkUser> markUserList = markUserQuestionService.listGroupUserByExamIdAndPaperNumberAndQuestionIdAndClassName(examId, paperNumber, m.getId(), null);
+            m.setMarkers(markUserList.stream().filter(MarkUser::getEnable).collect(Collectors.toList()));
+        }
+        markGroupTaskDto.setQuestions(markQuestionList);
+        // 分班阅参数
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        markGroupTaskDto.setClassMark(markPaper != null && markPaper.getClassMark());
+        markGroupTaskDto.setDoubleMark(markPaper != null && markPaper.getDoubleMark());
+        markGroupTaskDto.setMergeMarker(markPaper != null && markPaper.getMergeMarker());
+        return markGroupTaskDto;
+    }
+
+    @Override
+    public void updatePicListByQuestionId(QuestionPictureConfigParams questionPictureConfigParams) {
+        List<PictureConfig> pictureConfigs = questionPictureConfigParams.getPictureConfigs();
+        if (CollectionUtils.isEmpty(pictureConfigs)) {
+            throw ExceptionResultEnum.ERROR.exception("没有评卷区数据");
+        }
+        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkQuestion::getPicList, JSON.toJSONString(pictureConfigs))
+                .in(MarkQuestion::getId, questionPictureConfigParams.getQuestionIds());
+        this.update(updateWrapper);
+    }
+
+    @Override
+    public Integer countCurrentCountByExamIdAndPaperNumber(Long examId, String paperNumber) {
+        int total = 0;
+        List<MarkQuestion> markQuestionList = this.listByExamIdAndPaperNumber(examId, paperNumber);
+        if (CollectionUtils.isNotEmpty(markQuestionList)) {
+            for (MarkQuestion markQuestion : markQuestionList) {
+                total += markService.applyCurrentCount(markQuestion);
+            }
+        }
+        return total;
+    }
+
+    @Override
+    public MarkGroupSummaryProgressDto summaryGroupProgress(Long examId, String paperNumber) {
+        MarkGroupSummaryProgressDto markGroupSummaryProgressDto = new MarkGroupSummaryProgressDto();
+
+        // totalInfo
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        if (markPaper == null) {
+            return markGroupSummaryProgressDto;
+        }
+        markGroupSummaryProgressDto.setClassMark(markPaper.getClassMark());
+        MarkGroupTotalProgressDto markGroupTotalProgressDto = new MarkGroupTotalProgressDto();
+        markGroupTotalProgressDto.setStudentCount(markPaper.getStudentCount());
+        markGroupTotalProgressDto.setUploadCount(markStudentService.countUploadedByExamIdAndPaperNumber(examId, paperNumber));
+        markGroupTotalProgressDto.setAbsentCount(markStudentService.countAbsentByExamIdAndPaperNumber(examId, paperNumber));
+
+        int totalCount = markTaskService.countByExamIdAndPaperNumber(examId, paperNumber);
+        int markedCount = markTaskService.countByExamIdAndPaperNumberAndStatusIn(examId, paperNumber, Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED));
+        markGroupTotalProgressDto.setPercent(Calculator.divide2String(Calculator.multiply(markedCount, 100), Double.valueOf(totalCount), 2));
+        markGroupSummaryProgressDto.setTotalInfo(markGroupTotalProgressDto);
+
+        // groupInfo
+        List<MarkGroupProgressDto> markGroupProgressDtoList = new ArrayList<>();
+        List<MarkQuestion> markQuestionList = this.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
+        for (MarkQuestion markQuestion : markQuestionList) {
+            MarkGroupProgressDto markGroupProgressDto = new MarkGroupProgressDto();
+            markGroupProgressDto.setQuestionId(markQuestion.getId());
+            markGroupProgressDto.setTotalScore(markQuestion.getTotalScore());
+            markGroupProgressDto.setQuestionNumber(markQuestion.getQuestionNumber());
+            List<MarkUser> markUserList = markUserQuestionService.listGroupUserByExamIdAndPaperNumberAndQuestionIdAndClassName(examId, paperNumber, markQuestion.getId(), null);
+            markGroupProgressDto.setMarkerCount(markUserList.size());
+            markGroupProgressDto.setTaskCount(markQuestion.getTaskCount());
+            markGroupProgressDto.setMarkedCount(markQuestion.getMarkedCount());
+            markGroupProgressDto.setLeftCount(markGroupProgressDto.getTaskCount() - markGroupProgressDto.getMarkedCount());
+            markGroupProgressDto.setCurrentCount(markService.applyCurrentCount(markQuestion));
+            markGroupProgressDto.setPercent(Calculator.divide2String(Calculator.multiply(markGroupProgressDto.getMarkedCount(), 100), Double.valueOf(markGroupProgressDto.getTaskCount()), 2));
+            markGroupProgressDto.setArbitrateCount(markArbitrateHistoryService.waitArbitrateCount(examId, paperNumber, markQuestion.getId(), null));
+            markGroupProgressDtoList.add(markGroupProgressDto);
+        }
+        markGroupSummaryProgressDto.setGroupInfo(markGroupProgressDtoList);
+        return markGroupSummaryProgressDto;
+    }
+
+    @Override
+    public IPage<MarkGroupClassProgressDto> summaryGroupClassProgress(Long examId, String paperNumber, String className, Integer pageNumber, Integer pageSize) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+        if (markPaper.getClassMark()) {
+            Page<MarkGroupClassProgressDto> page = new Page<>(pageNumber, pageSize);
+            IPage<MarkGroupClassProgressDto> markGroupClassProgressDtoIPage = markUserClassService.pageClassByExamIdAndPaperNumber(page, examId, paperNumber, className);
+            for (MarkGroupClassProgressDto markGroupClassProgressDto : markGroupClassProgressDtoIPage.getRecords()) {
+                Long questionId = markGroupClassProgressDto.getQuestionId();
+                List<MarkTask> totalMarkTaskList = new ArrayList<>();
+                List<MarkUser> totalMarkUserList = new ArrayList<>();
+                totalMarkTaskList.addAll(markTaskService.listByExamIdAndPaperNumberAndQuestionIdAndUserIdAndClassName(examId, paperNumber, questionId, null, markGroupClassProgressDto.getClassName()));
+                totalMarkUserList.addAll(markUserQuestionService.listGroupUserByExamIdAndPaperNumberAndQuestionIdAndClassName(examId, paperNumber, questionId, markGroupClassProgressDto.getClassName()));
+
+                MarkQuestion markQuestion = this.getById(questionId);
+                List<MarkUser> markUserList = markUserClassService.listClassMarkerByExamIdAndPaperNumberAndQuestionIdAndClassName(examId, paperNumber, questionId, markGroupClassProgressDto.getClassName());
+                int count = 0;
+                for (MarkUser markUser : markUserList) {
+                    MarkUserQuestion markUserQuestion = markUserQuestionService.getByExamIdAndPaperNumberAndQuestionIdAndUserId(examId, paperNumber, questionId, markUser.getUserId());
+                    if (markUserQuestion != null) {
+                        int markerCount = markService.applyCurrentCount(markQuestion, markUserQuestion.getId());
+                        count += markerCount;
+                    }
+                }
+
+                // 待仲裁数量
+                int arbitrateCount = markArbitrateHistoryService.waitArbitrateCount(examId, paperNumber, questionId, markGroupClassProgressDto.getClassName());
+                markGroupClassProgressDto.setQuestionNumber(markQuestion.getQuestionNumber());
+                markGroupClassProgressDto.setMarkerCount(totalMarkUserList.size());
+                markGroupClassProgressDto.setTaskCount(totalMarkTaskList.size());
+                markGroupClassProgressDto.setMarkedCount(markTaskService.markedCount(totalMarkTaskList));
+                markGroupClassProgressDto.setLeftCount(markGroupClassProgressDto.getTaskCount() - markGroupClassProgressDto.getMarkedCount());
+                markGroupClassProgressDto.setCurrentCount(count);
+                markGroupClassProgressDto.setPercent(Calculator.divide2String(Calculator.multiply(markGroupClassProgressDto.getMarkedCount(), 100), Double.valueOf(markGroupClassProgressDto.getTaskCount()), 2));
+                markGroupClassProgressDto.setArbitrateCount(arbitrateCount);
+            }
+            return markGroupClassProgressDtoIPage;
+        }
+        return null;
+    }
+
+    @Override
+    public List<MarkGroupQuestionsDto> listGroupQuestions(Long examId, String paperNumber) {
+        List<MarkQuestion> markQuestionList = this.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
+        List<MarkGroupQuestionsDto> markGroupQuestionsDtoList = new ArrayList<>();
+        for (MarkQuestion markQuestion : markQuestionList) {
+            MarkGroupQuestionsDto markGroupQuestionsDto = new MarkGroupQuestionsDto();
+            markGroupQuestionsDto.setQuestionId(markQuestion.getId());
+            markGroupQuestionsDto.setQuestionNumber(markQuestion.getQuestionNumber());
+            markGroupQuestionsDtoList.add(markGroupQuestionsDto);
+        }
+        return markGroupQuestionsDtoList;
+    }
+
+    @Override
+    public void updateTaskCount(Long questionId, int taskCount) {
+        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkQuestion::getTaskCount, taskCount)
+                .eq(MarkQuestion::getId, questionId);
+        this.update(updateWrapper);
+    }
+
+    @Override
+    public void updateMarkedCount(Long questionId, int count) {
+        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkQuestion::getMarkedCount, count)
+                .eq(MarkQuestion::getId, questionId);
+        this.update(updateWrapper);
+    }
+
+    @Override
+    public void updateDoubleMarkByQuestionId(DoubleMarkParam doubleMarkParam) {
+        Long examId = doubleMarkParam.getExamId();
+        String paperNumber = doubleMarkParam.getPaperNumber();
+        Long questionId = doubleMarkParam.getQuestionId();
+        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);
+
+        UpdateWrapper<MarkQuestion> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkQuestion::getDoubleRate, doubleMarkParam.getDoubleRate())
+                .set(MarkQuestion::getArbitrateThreshold, doubleMarkParam.getArbitrateThreshold())
+                .set(MarkQuestion::getScorePolicy, doubleMarkParam.getScorePolicy())
+                .eq(MarkQuestion::getId, doubleMarkParam.getQuestionId());
+        this.update(updateWrapper);
+
+        // 单、又评切换、双评比例修改,删除任务
+        if (!markQuestion.getDoubleRate().equals(doubleMarkParam.getDoubleRate())) {
+            this.updateMarkedCount(questionId, 0);
+            this.updateTaskCount(questionId, 0);
+            if (lockService.trylock(LockType.QUESTION_UPDATE, questionId)) {
+                markSyncService.deleteMarkTask(markQuestion);
+            }
+        }
+
+
+    }
+
+    @Override
+    public MarkQuestionSubjectiveStepStatusDto stepStatus(Long examId, String paperNumber) {
+        List<MarkQuestion> markQuestionList = this.listByExamIdAndPaperNumberAndObjective(examId, paperNumber, false);
+        Boolean subjectiveStruct = false, subjectiveMarker = false, subjectiveMarkerClass = false, objectiveAnswer = false;
+        if (CollectionUtils.isNotEmpty(markQuestionList)) {
+            // 主观题结构,小题分是否设置
+            long countStruct = markQuestionList.stream().filter(m -> m.getTotalScore() == null || m.getIntervalScore() == null).count();
+            subjectiveStruct = countStruct == 0;
+
+            if (subjectiveStruct) {
+                // 主观题设置评卷员
+                long countPic = markQuestionList.stream().filter(m -> StringUtils.isBlank(m.getPicList())).count();
+                subjectiveMarker = countPic == 0;
+                if (subjectiveMarker) {
+                    for (MarkQuestion markQuestion : markQuestionList) {
+                        List<MarkUserQuestion> markUserQuestionList = markUserQuestionService.listByExamIdAndPaperNumberAndQuestionId(examId, paperNumber, markQuestion.getId());
+                        if (CollectionUtils.isEmpty(markUserQuestionList)) {
+                            subjectiveMarker = false;
+                            break;
+                        }
+                    }
+                }
+
+                if (subjectiveMarker) {
+                    // 分班阅
+                    MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
+                    subjectiveMarkerClass = markPaper != null && markPaper.getClassMark();
+                    if (subjectiveMarkerClass) {
+                        MarkStudentQuery markStudentQuery = new MarkStudentQuery();
+                        markStudentQuery.setExamId(examId);
+                        markStudentQuery.setPaperNumber(paperNumber);
+                        List<MarkStudentVo> markStudentList = markStudentService.listMarkStudentVo(markStudentQuery);
+                        List<String> teachClasses = markStudentList.stream().filter(m -> StringUtils.isNotBlank(m.getTeachClassName())).map(MarkStudentVo::getTeachClassName).distinct().collect(Collectors.toList());
+                        for (MarkQuestion markQuestion : markQuestionList) {
+                            List<String> classNames = markUserQuestionService.countClassByExamIdAndPaperNumberAndQuestionId(examId, paperNumber, markQuestion.getId());
+                            if (!CollectionUtils.isEqualCollection(teachClasses, classNames)) {
+                                subjectiveMarkerClass = false;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (subjectiveMarkerClass) {
+                        // 客观题标答
+                        List<MarkQuestionAnswer> markQuestionAnswerList = markQuestionAnswerService.listByExamIdAndPaperNumberAndPaperType(examId, paperNumber, null);
+                        long countAnswer = markQuestionAnswerList.stream().filter(m -> StringUtils.isBlank(m.getAnswer()) || m.getObjectivePolicy() == null).count();
+                        objectiveAnswer = countAnswer == 0;
+                    }
+                }
+            }
+        }
+
+        return new MarkQuestionSubjectiveStepStatusDto(subjectiveStruct, subjectiveMarker, subjectiveMarkerClass, objectiveAnswer);
+    }
+
+    @Override
+    public void updatePic(String content, List<MarkQuestion> markQuestions) {
+        List<SubPictureConfig> pictureConfigs = CardParseUtils.parseCardPic(content, markQuestions);
+        List<SubPictureConfig> subPictureConfigList;
+        for (MarkQuestion markQuestion : markQuestions) {
+            // 填空题区域为一个整体
+            if (markQuestion.getQuestionType() == 4) {
+                subPictureConfigList = pictureConfigs.stream().filter(m -> m.getMainNumber().equals(markQuestion.getMainNumber())).collect(Collectors.toList());
+            } else {
+                subPictureConfigList = pictureConfigs.stream().filter(m -> m.getQuestionNumber().equals(markQuestion.getQuestionNumber())).collect(Collectors.toList());
+            }
+            markQuestion.setPicList(SubPictureConfig.transfer(subPictureConfigList));
+        }
+        this.updateBatchById(markQuestions);
+    }
+
+    @Override
+    public long countUnBindMarkerByExamIdAndPaperNumberAndObjective(Long examId, String paperNumber, boolean objective) {
+        return this.baseMapper.countUnBindMarkerByExamIdAndPaperNumberAndObjective(examId, paperNumber, objective);
+    }
+
+    @Override
+    public long countByExamIdAndPaperNumberAndObjective(Long examId, String paperNumber, boolean objective) {
+        QueryWrapper<MarkQuestion> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkQuestion::getExamId, examId)
+                .eq(MarkQuestion::getPaperNumber, paperNumber)
+                .eq(MarkQuestion::getObjective, objective);
+        return this.count(queryWrapper);
+    }
+}

+ 122 - 3
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkUserQuestionServiceImpl.java

@@ -1,20 +1,28 @@
 package com.qmth.teachcloud.mark.service.impl;
 
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.common.collect.ImmutableMap;
 import com.qmth.teachcloud.common.bean.dto.MarkTaskSmsDto;
 import com.qmth.teachcloud.common.bean.dto.mark.MarkUser;
+import com.qmth.teachcloud.common.bean.result.SmsResponseResult;
 import com.qmth.teachcloud.common.entity.MarkQuestion;
+import com.qmth.teachcloud.common.entity.SysConfig;
 import com.qmth.teachcloud.common.entity.SysUser;
 import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
+import com.qmth.teachcloud.common.enums.MessageEnum;
 import com.qmth.teachcloud.common.enums.mark.MarkPaperStatus;
+import com.qmth.teachcloud.common.service.SysConfigService;
 import com.qmth.teachcloud.common.service.SysUserService;
 import com.qmth.teachcloud.common.util.ServletUtil;
+import com.qmth.teachcloud.common.util.SmsSendUtil;
 import com.qmth.teachcloud.mark.bean.marker.MarkerAddParam;
+import com.qmth.teachcloud.mark.dto.BasicMessageDto;
 import com.qmth.teachcloud.mark.dto.mark.entrance.MarkEntranceDto;
 import com.qmth.teachcloud.mark.dto.mark.manage.MarkQualityChartDto;
 import com.qmth.teachcloud.mark.dto.mark.manage.MarkQualityDto;
@@ -30,14 +38,14 @@ import com.qmth.teachcloud.mark.mapper.MarkUserQuestionMapper;
 import com.qmth.teachcloud.mark.service.*;
 import com.qmth.teachcloud.mark.utils.Calculator;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -68,6 +76,12 @@ public class MarkUserQuestionServiceImpl extends ServiceImpl<MarkUserQuestionMap
     @Resource
     private LockService lockService;
 
+    @Resource
+    private SmsSendUtil smsSendUtil;
+
+    @Resource
+    private SysConfigService sysConfigService;
+
     @Override
     public IPage<MarkEntranceDto> listEntranceGroup(Long examId, Long openCollegeId, Long courseId, String paperNumber, Integer pageNumber, Integer pageSize) {
         SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
@@ -413,4 +427,109 @@ public class MarkUserQuestionServiceImpl extends ServiceImpl<MarkUserQuestionMap
     public List<MarkTaskSmsDto> findMarkTaskSms(Long examId, Long courseId, String paperNumber) {
         return this.baseMapper.findMarkTaskSms(examId, courseId, paperNumber);
     }
+
+    /**
+     * 短信count
+     *
+     * @param userId
+     * @param mobileNumber
+     * @param condition
+     * @return
+     */
+    @Override
+    public int smsConditionCount(Long userId, String mobileNumber, String condition) {
+        return this.baseMapper.smsConditionCount(userId, mobileNumber, condition);
+    }
+
+    /**
+     * 保存message和备注
+     *
+     * @param schoolId
+     * @param userId
+     * @param userName
+     * @param mobileNumber
+     * @param paperNumber
+     * @param courseCode
+     * @param variableParams
+     * @param messageType
+     * @param createId
+     * @param remark
+     */
+    @Override
+    @Transactional
+    public void saveMessageSendLogRemark(Long schoolId, Long userId, String userName, String mobileNumber, String paperNumber, String courseCode, String variableParams, MessageEnum messageType, Long createId, String remark) {
+        BasicMessageDto basicMessageDto = null;
+        String templateCode = null;
+        try {
+            // code和content
+            Map<String, String> enumInfo = smsSendUtil.getCodeAndContentByEnum(messageType);
+            if (!enumInfo.containsKey("templateCode")) {
+                throw ExceptionResultEnum.ERROR.exception("未找到短信模板Code");
+            }
+            templateCode = enumInfo.get("templateCode");
+            if (StringUtils.isBlank(templateCode)) {
+                throw ExceptionResultEnum.ERROR.exception("阿里云短信模板Code必填");
+            }
+
+            // 发送短信参数配置验证
+            SysConfig sysConfig = sysConfigService.getByKey("sys.message.enable");
+            if (sysConfig == null) {
+                throw ExceptionResultEnum.ERROR.exception("未找到发送短信开关配置参数,默认不发送");
+            }
+            if (sysConfig.getConfigValue() == null) {
+                throw ExceptionResultEnum.ERROR.exception("未正确配置发送短信开关参数,默认不发送");
+            }
+            if (sysConfig.getConfigValue().equals("false")) {
+                throw ExceptionResultEnum.ERROR.exception("发送短信开关设置为已关闭");
+            }
+
+            // 参数校验
+            if (StringUtils.isBlank(userName)) {
+                throw ExceptionResultEnum.ERROR.exception("用户名称必填");
+            }
+            if (StringUtils.isBlank(mobileNumber)) {
+                throw ExceptionResultEnum.ERROR.exception("用户手机号必填");
+            }
+            if (StringUtils.isBlank(variableParams)) {
+                throw ExceptionResultEnum.ERROR.exception("短信内容参数值必填");
+            }
+
+            // 调用阿里云短信平台发送短信
+            Map<String, Object> templateParam = JSON.parseObject(variableParams, Map.class);
+            SmsResponseResult smsResponseResult = smsSendUtil.sendSms(mobileNumber, templateCode, templateParam);
+            if (!SmsSendUtil.OK.equals(smsResponseResult.getCode())) {
+                throw ExceptionResultEnum.ERROR.exception("阿里云短信发送接口调用失败");
+            }
+            basicMessageDto = new BasicMessageDto(schoolId, userId, userName, mobileNumber, paperNumber,
+                    courseCode, variableParams, messageType, createId,
+                    remark, templateCode, smsResponseResult.getCode(), smsResponseResult.getMessage());
+        } catch (Exception e) {
+            basicMessageDto.setErrorInfo(e.getMessage());
+        } finally {
+            this.baseMapper.insertSms(basicMessageDto);
+        }
+    }
+
+    /**
+     * 发送阅卷待办短信
+     *
+     * @param examId
+     * @param courseId
+     * @param paperNumber
+     */
+    @Override
+    @Transactional
+    public void smsMarkTask(Long examId, Long courseId, String paperNumber) {
+        List<MarkTaskSmsDto> markTaskSmsDtoList = this.findMarkTaskSms(examId, courseId, paperNumber);
+        if (CollectionUtils.isNotEmpty(markTaskSmsDtoList)) {
+            for (MarkTaskSmsDto m : markTaskSmsDtoList) {
+                StringJoiner stringJoiner = new StringJoiner("-");
+                stringJoiner.add(m.getExamId().toString()).add(m.getCourseId().toString()).add(m.getPaperNumber());
+                int count = this.smsConditionCount(m.getUserId(), m.getMobileNumber(), stringJoiner.toString());
+                if (count == 0) {
+                    this.saveMessageSendLogRemark(m.getSchoolId(), m.getUserId(), m.getRealName(), m.getMobileNumber(), m.getPaperNumber(), null, JSON.toJSONString(ImmutableMap.of("courseName", m.getCourseName(), "examName", m.getExamName())), MessageEnum.NOTICE_OF_MARK_TASK_CREATED, -1L, stringJoiner.toString());
+                }
+            }
+        }
+    }
 }

+ 36 - 0
teachcloud-mark/src/main/resources/mapper/MarkUserQuestionMapper.xml

@@ -293,4 +293,40 @@
             su.real_name
         order by muq.user_id
     </select>
+
+    <select id="smsConditionCount" resultType="java.lang.Integer">
+        select count(1) from basic_message bm
+        where bm.user_id = #{userId}
+          and bm.mobile_number = #{mobileNumber}
+          and bm.message_type  = 'NOTICE_OF_MARK_TASK_CREATED'
+          and bm.send_status = 'OK'
+          and (bm.remark is not null and bm.remark = #{condition})
+    </select>
+
+    <insert id="insertSms" parameterType="com.qmth.teachcloud.mark.dto.BasicMessageDto">
+        INSERT INTO basic_message
+        (id, school_id, org_id, user_id, user_name, mobile_number, paper_number, course_code, message_type, business_operate, business_id, template_code, variable_params, template_content, send_status, send_result, remark, create_id, create_time, update_id, update_time, resend_count)
+        VALUES(#{basicMessageDto.id},
+               #{basicMessageDto.schoolId},
+               NULL,
+               #{basicMessageDto.userId},
+               #{basicMessageDto.userName},
+               #{basicMessageDto.mobileNumber},
+               #{basicMessageDto.paperNumber},
+               NULL,
+               #{basicMessageDto.messageType},
+               #{basicMessageDto.businessOperate},
+               NULL,
+               #{basicMessageDto.templateCode},
+               #{basicMessageDto.variableParams},
+               NULL,
+               #{basicMessageDto.sendStatus},
+               #{basicMessageDto.sendResult},
+               #{basicMessageDto.remark},
+               #{basicMessageDto.createId},
+               #{basicMessageDto.createTime},
+               NULL,
+               #{basicMessageDto.updateTime},
+               #{basicMessageDto.resendCount});
+    </insert>
 </mapper>