caozixuan 1 жил өмнө
parent
commit
a3edba6780
13 өөрчлөгдсөн 654 нэмэгдсэн , 44 устгасан
  1. 157 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/dto/SubjectiveStructDto.java
  2. 10 1
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/CourseTargetService.java
  3. 10 2
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/CourseTargetServiceImpl.java
  4. 1 1
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/TCPaperStructServiceImpl.java
  5. 61 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/execute/SyncSubjectiveStructImportService.java
  6. 9 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/service/TaskLogicService.java
  7. 262 8
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/service/impl/TaskLogicServiceImpl.java
  8. 1 1
      distributed-print/src/main/java/com/qmth/distributed/print/api/mark/MarkSettingController.java
  9. BIN
      distributed-print/src/main/resources/temps/objectiveStruct.xlsx
  10. BIN
      distributed-print/src/main/resources/temps/subjectiveStruct.xlsx
  11. 0 8
      distributed-print/src/test/java/com/qmth/distributed/print/ServiceTest.java
  12. 2 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/enums/ImportTemplateEnum.java
  13. 141 23
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/util/ConvertUtil.java

+ 157 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/dto/SubjectiveStructDto.java

@@ -0,0 +1,157 @@
+package com.qmth.distributed.print.business.bean.dto;
+
+import com.qmth.boot.tools.excel.annotation.ExcelColumn;
+import com.qmth.teachcloud.common.annotation.ExcelError;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description: 主观题结构Dto
+ * @Author: CaoZixuan
+ * @Date: 2024-04-09
+ */
+public class SubjectiveStructDto {
+
+    @ApiModelProperty(value = "课程代码")
+    @ExcelColumn(name = "课程代码", index = 1, nullable = true)
+    private String courseCode;
+
+    @ApiModelProperty(value = "课程名称")
+    @ExcelColumn(name = "课程名称", index = 2, nullable = true)
+    private String courseName;
+
+    @ApiModelProperty(value = "试卷编号")
+    @ExcelColumn(name = "试卷编号", index = 3, nullable = true)
+    private String paperNumber;
+
+    @ApiModelProperty(value = "大题名称")
+    @ExcelColumn(name = "大题名称", index = 4, nullable = true)
+    private String mainTitle;
+
+    @ApiModelProperty(value = "大题号(只能用小写数字)")
+    @ExcelColumn(name = "大题号(只能用小写数字)", index = 5, nullable = true)
+    private Integer mainNumber;
+
+    @ApiModelProperty(value = "小题号(只能用小写数字)")
+    @ExcelColumn(name = "小题号(只能用小写数字)", index = 6, nullable = true)
+    private Integer subNumber;
+
+    @ApiModelProperty(value = "小题满分")
+    @ExcelColumn(name = "小题满分", index = 7, nullable = true)
+    private Double totalScore;
+
+    @ApiModelProperty(value = "评卷间隔分")
+    @ExcelColumn(name = "间隔分", index = 8, nullable = true)
+    private Double intervalScore;
+
+    @ApiModelProperty(value = "评卷分组序号")
+    @ExcelColumn(name = "评卷分组(只能用小写数字)", index = 9, nullable = true)
+    private Integer groupNumber;
+
+    @ApiModelProperty(value = "评卷员数量(系统生成)")
+    @ExcelColumn(name = "评卷员数量(系统生成)", index = 10)
+    private Integer markerCount;
+
+    @ApiModelProperty(value = "绑定工号(英文逗号分隔)")
+    @ExcelColumn(name = "绑定工号(英文逗号分隔)", index = 11)
+    private String markerCodes;
+
+    @ApiModelProperty(value = "错误信息")
+    @ExcelError
+    private String errorMsg;
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getPaperNumber() {
+        return paperNumber;
+    }
+
+    public void setPaperNumber(String paperNumber) {
+        this.paperNumber = paperNumber;
+    }
+
+    public String getMainTitle() {
+        return mainTitle;
+    }
+
+    public void setMainTitle(String mainTitle) {
+        this.mainTitle = mainTitle;
+    }
+
+    public Integer getMainNumber() {
+        return mainNumber;
+    }
+
+    public void setMainNumber(Integer mainNumber) {
+        this.mainNumber = mainNumber;
+    }
+
+    public Integer getSubNumber() {
+        return subNumber;
+    }
+
+    public void setSubNumber(Integer subNumber) {
+        this.subNumber = subNumber;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Double getIntervalScore() {
+        return intervalScore;
+    }
+
+    public void setIntervalScore(Double intervalScore) {
+        this.intervalScore = intervalScore;
+    }
+
+    public Integer getGroupNumber() {
+        return groupNumber;
+    }
+
+    public void setGroupNumber(Integer groupNumber) {
+        this.groupNumber = groupNumber;
+    }
+
+    public Integer getMarkerCount() {
+        return markerCount;
+    }
+
+    public void setMarkerCount(Integer markerCount) {
+        this.markerCount = markerCount;
+    }
+
+    public String getMarkerCodes() {
+        return markerCodes;
+    }
+
+    public void setMarkerCodes(String markerCodes) {
+        this.markerCodes = markerCodes;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+}

+ 10 - 1
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/CourseTargetService.java

@@ -56,7 +56,16 @@ public interface CourseTargetService extends IService<CourseTarget> {
      *
      * @param examId      考试id
      * @param paperNumber 试卷编号
+     * @param userId      课程创建人id
      * @return 试卷结构
      */
-    List<PaperStructDimensionResult> findTikuPaperStruct(Long examId, String paperNumber);
+    List<PaperStructDimensionResult> findTikuPaperStruct(Long examId, String paperNumber, Long userId);
+
+    /**
+     * 查询题库试卷结构(知识点)
+     *
+     * @param teachCourseId 教学课程id
+     * @return 试卷结构
+     */
+    List<PaperStructDimensionResult> findTikuPaperStruct(Long teachCourseId);
 }

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

@@ -261,7 +261,7 @@ public class CourseTargetServiceImpl extends ServiceImpl<CourseTargetMapper, Cou
     }
 
     @Override
-    public List<PaperStructDimensionResult> findTikuPaperStruct(Long examId, String paperNumber) {
+    public List<PaperStructDimensionResult> findTikuPaperStruct(Long examId, String paperNumber, Long userId) {
         List<PaperStructDimensionResult> result = new ArrayList<>();
         ExamTask examTask = examTaskService.getOne(new QueryWrapper<ExamTask>().lambda().eq(ExamTask::getExamId, examId)
                 .eq(ExamTask::getPaperNumber, paperNumber).last(SystemConstant.LIMIT1));
@@ -269,7 +269,6 @@ public class CourseTargetServiceImpl extends ServiceImpl<CourseTargetMapper, Cou
             throw ExceptionResultEnum.ERROR.exception("命题任务不存在");
         }
         String courseCode = examTask.getCourseCode();
-        Long userId = examTask.getUserId();
         TeachCourse teachCourse = teachCourseService.findByExamIdCourseCodeAndUserId(examId, courseCode, userId);
         List<CourseTarget> courseTargetList = this.list(
                 new QueryWrapper<CourseTarget>().lambda().eq(CourseTarget::getTeachCourseId, teachCourse.getId()));
@@ -364,6 +363,15 @@ public class CourseTargetServiceImpl extends ServiceImpl<CourseTargetMapper, Cou
         return result;
     }
 
+    @Override
+    public List<PaperStructDimensionResult> findTikuPaperStruct(Long teachCourseId) {
+        TeachCourse teachCourse = teachCourseService.getById(teachCourseId);
+        if (Objects.isNull(teachCourse)){
+            throw ExceptionResultEnum.ERROR.exception("教学课程不存在");
+        }
+        return this.findTikuPaperStruct(teachCourse.getExamId(), teachCourse.getCourseCode(), teachCourse.getUserId());
+    }
+
     /**
      * 构建知识点占用情况对象
      *

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

@@ -209,7 +209,7 @@ public class TCPaperStructServiceImpl extends ServiceImpl<TCPaperStructMapper, T
             SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
             StringJoiner errorData = new StringJoiner("");
             StringJoiner successData = new StringJoiner("");
-            List<PaperStructDimensionResult> paperStructDimensionResultList = courseTargetService.findTikuPaperStruct(examId, paperNumber);
+            List<PaperStructDimensionResult> paperStructDimensionResultList = courseTargetService.findTikuPaperStruct(teachCourseId);
             if (!CollectionUtils.isEmpty(paperStructDimensionResultList)) {
                 List<String> paperStructList = new ArrayList<>(paperStructDimensionResultList.size()), paperStructDbList = new ArrayList<>(paperStructDimensionResultList.size());
                 for (PaperStructDimensionResult p : paperStructDimensionResultList) {

+ 61 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/execute/SyncSubjectiveStructImportService.java

@@ -0,0 +1,61 @@
+package com.qmth.distributed.print.business.templete.execute;
+
+import cn.hutool.core.date.DateUtil;
+import com.qmth.distributed.print.business.templete.importData.SyncImportTaskTemplate;
+import com.qmth.distributed.print.business.templete.service.TaskLogicService;
+import com.qmth.teachcloud.common.contant.SpringContextHolder;
+import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.entity.TBTask;
+import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
+import com.qmth.teachcloud.common.enums.TaskResultEnum;
+import com.qmth.teachcloud.common.enums.TaskStatusEnum;
+import com.qmth.teachcloud.common.service.TBTaskService;
+import com.qmth.teachcloud.common.util.Result;
+import com.qmth.teachcloud.common.util.ResultUtil;
+import org.springframework.stereotype.Service;
+
+import java.io.InputStream;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.StringJoiner;
+
+/**
+ * @Description: 试卷主观题结构导入同步任务
+ * @Author: CaoZixuan
+ * @Date: 2024/04/09
+ */
+@Service
+public class SyncSubjectiveStructImportService extends SyncImportTaskTemplate {
+
+    public static final String OBJ_TITLE = "主观题结构导入";
+
+    @Override
+    public Result importTask(Map<String, Object> map) throws Exception {
+        TBTask tbTask = (TBTask) map.get(SystemConstant.TASK);
+        StringJoiner stringJoinerSummary = new StringJoiner("\n").add(
+                MessageFormat.format("{0}{1}{2}", DateUtil.format(new Date(), SystemConstant.DEFAULT_DATE_PATTERN),
+                        BEGIN_TITLE, OBJ_TITLE));
+        tbTask.setStatus(TaskStatusEnum.RUNNING);
+        TBTaskService tbTaskService = SpringContextHolder.getBean(TBTaskService.class);
+        tbTaskService.updateById(tbTask);
+        try (InputStream inputStream = super.getUploadFileInputStream(tbTask)) {
+            map.put("inputStream", inputStream);
+            TaskLogicService taskLogicService = SpringContextHolder.getBean(TaskLogicService.class);
+            // 执行导入考务数据
+            taskLogicService.executeImportObjectiveStructLogic(map);
+            tbTask.setResult(TaskResultEnum.SUCCESS);
+            stringJoinerSummary.add("导入主观题结构成功");
+        } catch (Exception e) {
+            // 导入异常错误信息写入summary
+            stringJoinerSummary.add("导入主观题结构失败," + EXCEPTION_DATA + System.lineSeparator() + e.getMessage());
+            tbTask.setResult(TaskResultEnum.ERROR);
+            throw ExceptionResultEnum.ERROR.exception("导入主观题结构失败,请通过'导入结果查询'查看错误原因");
+        } finally {//生成文件
+            tbTask.setSummary(stringJoinerSummary.toString());
+            tbTask.setStatus(TaskStatusEnum.FINISH);
+            tbTaskService.updateById(tbTask);
+        }
+        return ResultUtil.ok();
+    }
+}

+ 9 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/service/TaskLogicService.java

@@ -153,4 +153,13 @@ public interface TaskLogicService {
      * @throws Exception 异常
      */
     Map<String, Object> executeImportObjectiveStructLogic(Map<String, Object> map) throws Exception;
+
+    /**
+     * 处理主观题结构导入
+     *
+     * @param map 数据源
+     * @return 结果
+     * @throws Exception 异常
+     */
+    Map<String, Object> executeImportSubjectiveStructLogic(Map<String, Object> map) throws Exception;
 }

+ 262 - 8
distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/service/impl/TaskLogicServiceImpl.java

@@ -27,7 +27,9 @@ import com.qmth.teachcloud.common.base.BaseEntity;
 import com.qmth.teachcloud.common.bean.dto.excel.*;
 import com.qmth.teachcloud.common.bean.dto.excel.export.BasicStudentErrorExportDto;
 import com.qmth.teachcloud.common.bean.dto.excel.export.SysUserErrorExportDto;
+import com.qmth.teachcloud.common.bean.dto.mark.MarkUser;
 import com.qmth.teachcloud.common.bean.params.ArraysParams;
+import com.qmth.teachcloud.common.bean.params.UserSaveParams;
 import com.qmth.teachcloud.common.bean.result.ExcelResult;
 import com.qmth.teachcloud.common.bean.vo.PaperInfoVo;
 import com.qmth.teachcloud.common.bean.vo.PrintPathVo;
@@ -39,7 +41,7 @@ import com.qmth.teachcloud.common.service.*;
 import com.qmth.teachcloud.common.util.*;
 import com.qmth.teachcloud.common.util.excel.ExcelError;
 import com.qmth.teachcloud.mark.entity.MarkPaper;
-import com.qmth.teachcloud.mark.params.MarkQuestionParams;
+import com.qmth.teachcloud.mark.service.MarkGroupService;
 import com.qmth.teachcloud.mark.service.MarkPaperService;
 import com.qmth.teachcloud.mark.service.MarkQuestionService;
 import com.qmth.teachcloud.mark.service.MarkStudentService;
@@ -71,6 +73,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @Description: 任务处理逻辑impl
@@ -107,11 +110,11 @@ public class TaskLogicServiceImpl implements TaskLogicService {
     TBTaskPdfService tbTaskPdfService;
     @Resource
     BasicCardRuleService basicCardRuleService;
-    @Autowired
+    @Resource
     ExamDetailCourseService examDetailCourseService;
-    @Autowired
+    @Resource
     BasicStudentService basicStudentService;
-    @Autowired
+    @Resource
     ExamTaskPrintService examTaskPrintService;
     @Resource
     BasicExamService basicExamService;
@@ -133,9 +136,9 @@ public class TaskLogicServiceImpl implements TaskLogicService {
     @Lazy
     PrintCommonService printCommonService;
     @Resource
-    private DownloadService downloadService;
+    DownloadService downloadService;
     @Resource
-    private TeachClazzService teachClazzService;
+    TeachClazzService teachClazzService;
     @Resource
     DictionaryConfig dictionaryConfig;
     @Resource
@@ -152,6 +155,10 @@ public class TaskLogicServiceImpl implements TaskLogicService {
     MarkStudentService markStudentService;
     @Resource
     MarkQuestionService markQuestionService;
+    @Resource
+    MarkGroupService markGroupService;
+    @Resource
+    SysRoleService sysRoleService;
 
     /**
      * 创建pdf前置条件
@@ -2355,7 +2362,6 @@ public class TaskLogicServiceImpl implements TaskLogicService {
             // 公共异常提示
             String logicErrorCommonNotice = "";
 
-            // 行索引
             String courseCode = objectiveStructDto.getCourseCode();
             String courseName = objectiveStructDto.getCourseName();
             String paperNumber = objectiveStructDto.getPaperNumber();
@@ -2367,7 +2373,7 @@ public class TaskLogicServiceImpl implements TaskLogicService {
             Integer questionType = objectiveStructDto.getQuestionType();
             String errorMsg = objectiveStructDto.getErrorMsg();
 
-            // 1.判断题号重复
+            // 1.题号重复校验
             if (SystemConstant.strNotNull(paperNumber) && mainNumber != null && subNumber != null) {
                 logicErrorCommonNotice = String.format("试卷编号为[%s],大题号为[%s],小题号为[%s]的数据异常 : ", paperNumber, mainNumber,
                         subNumber);
@@ -2467,6 +2473,254 @@ public class TaskLogicServiceImpl implements TaskLogicService {
         return map;
     }
 
+    @Transactional
+    @Override
+    public Map<String, Object> executeImportSubjectiveStructLogic(Map<String, Object> map) throws Exception {
+        SysUser requestUser = (SysUser) map.get(SystemConstant.USER);
+        InputStream inputStream = (InputStream) map.get("inputStream");
+        Long examId = SystemConstant.convertIdToLong(String.valueOf(map.get("examId")));
+        Long schoolId = requestUser.getSchoolId();
+        BasicExam basicExam = basicExamService.getById(examId);
+        if (Objects.isNull(basicExam)) {
+            throw ExceptionResultEnum.ERROR.exception("考试不存在");
+        }
+        if (!ExamModelEnum.MODEL4.equals(basicExam.getExamModel())) {
+            throw ExceptionResultEnum.ERROR.exception("仅针对模式4的考试可以导入");
+        }
+        SysRole markerRole = sysRoleService.getOne(
+                new QueryWrapper<SysRole>().lambda().eq(SysRole::getType, RoleTypeEnum.MARKER));
+        if (Objects.isNull(markerRole)) {
+            throw ExceptionResultEnum.ERROR.exception("缺少评卷员角色");
+        }
+
+        // 课程编号名称校验
+        Map<String, String> courseCheckMap = basicCourseService.list(
+                        new QueryWrapper<BasicCourse>().lambda().eq(BasicCourse::getSchoolId, schoolId)).stream()
+                .collect(Collectors.toMap(BasicCourse::getCode, BasicCourse::getName));
+        List<MarkPaper> markPaperList = markPaperService.list(
+                new QueryWrapper<MarkPaper>().lambda().eq(MarkPaper::getExamId, examId));
+        // 题号重复校验
+        // 导入的客观题题号集合
+        Set<String> questionNumberCheckSet = new HashSet<>();
+        // 该试卷已存在的客观题题号集合
+        Set<String> dbObjectiveQuestionNumberSet = markQuestionService.list(
+                        new QueryWrapper<MarkQuestion>().lambda().eq(MarkQuestion::getExamId, examId)
+                                .eq(MarkQuestion::getObjective, true)).stream()
+                .map(e -> e.getPaperNumber() + SystemConstant.HYPHEN + e.getMainNumber() + SystemConstant.HYPHEN + e.getSubNumber()).collect(Collectors.toSet());
+
+        ExcelResult<SubjectiveStructDto> excelResult = ConvertUtil.analyzeExcel(inputStream, SubjectiveStructDto.class,
+                true, 0);
+        List<SubjectiveStructDto> subjectiveStructDtoList = excelResult.getDatasource();
+        for (SubjectiveStructDto subjectiveStructDto : subjectiveStructDtoList) {
+            List<String> excelErrorList = new ArrayList<>();
+            // 公共异常提示
+            String excelErrorCommonNotice = "";
+
+            String courseCode = subjectiveStructDto.getCourseCode();
+            String courseName = subjectiveStructDto.getCourseName();
+            String paperNumber = subjectiveStructDto.getPaperNumber();
+            String mainTitle = subjectiveStructDto.getMainTitle();
+            Integer mainNumber = subjectiveStructDto.getMainNumber();
+            Integer subNumber = subjectiveStructDto.getSubNumber();
+            Double totalScore = subjectiveStructDto.getTotalScore();
+            Double intervalScore = subjectiveStructDto.getIntervalScore();
+            Integer groupNumber = subjectiveStructDto.getGroupNumber();
+            Integer markerCount = subjectiveStructDto.getMarkerCount();
+            String markerCodes = subjectiveStructDto.getMarkerCodes();
+            String errorMsg = subjectiveStructDto.getErrorMsg();
+
+            // 1.题号重复校验
+            if (SystemConstant.strNotNull(paperNumber) && mainNumber != null && subNumber != null) {
+                excelErrorCommonNotice = String.format("试卷编号为[%s],大题号为[%s],小题号为[%s]的数据异常 : ", paperNumber, mainNumber,
+                        subNumber);
+                String questionNumberKey = paperNumber + SystemConstant.HYPHEN + mainNumber + SystemConstant.HYPHEN + subNumber;
+                if (questionNumberCheckSet.contains(questionNumberKey)) {
+                    excelErrorList.add("excel中题号重复");
+                } else {
+                    questionNumberCheckSet.add(questionNumberKey);
+                }
+                if (dbObjectiveQuestionNumberSet.contains(questionNumberKey)) {
+                    excelErrorList.add("该题号已存在客观题中");
+                }
+            }
+
+            // 2.校验课程代码,课程名称,试卷在考试下是否存在
+            if (SystemConstant.strNotNull(courseCode) && SystemConstant.strNotNull(courseName) && SystemConstant.strNotNull(paperNumber)) {
+                if (!courseCheckMap.containsKey(courseCode)) {
+                    excelErrorList.add("课程代码不存在");
+                } else if (!courseCheckMap.get(courseCode).equals(courseName)) {
+                    excelErrorList.add("课程代码名称不匹配");
+                }
+                if (markPaperList.stream().noneMatch(m -> m.getPaperNumber().equals(paperNumber) && m.getCourseCode().equals(courseCode))) {
+                    excelErrorList.add("试卷不存在");
+                }
+            }
+
+            // 3.评卷员校验
+            if (Objects.isNull(markerCount) && Objects.isNull(markerCodes)){
+                excelErrorList.add("[评卷员数量(系统生成)]和[绑定工号(英文逗号分隔)]不能同时为空");
+            }
+
+            // 整理异常信息
+            if (CollectionUtils.isNotEmpty(excelErrorList)) {
+                if (SystemConstant.strNotNull(errorMsg)) {
+                    errorMsg = errorMsg + "、" + excelErrorCommonNotice + String.join("、", excelErrorList);
+                } else {
+                    errorMsg = excelErrorCommonNotice + String.join("、", excelErrorList);
+                }
+                subjectiveStructDto.setErrorMsg(errorMsg);
+            }
+        }
+        if (subjectiveStructDtoList.stream().anyMatch(e -> SystemConstant.strNotNull(e.getErrorMsg()))) {
+            // 抛出excel解析异常
+            throw ExceptionResultEnum.ERROR.exception(
+                    subjectiveStructDtoList.stream().map(SubjectiveStructDto::getErrorMsg).filter(SystemConstant::strNotNull).collect(Collectors.joining(System.lineSeparator())));
+        } else {
+            List<String> logicErrorList = new ArrayList<>();
+            // 试卷编号-> 题目集合 Map
+            Map<String, List<SubjectiveStructDto>> paperQuestionsMap = subjectiveStructDtoList.stream().collect(Collectors.groupingBy(SubjectiveStructDto::getPaperNumber));
+            paperQuestionsMap.forEach((paperNumber, paperQuestionList) -> {
+                if (markGroupService.countByExamIdAndPaperNumber(examId,paperNumber) > 0){
+                    logicErrorList.add(String.format("[试卷编号]%s,已存在分组,请先删除该试卷的所有分组再导入试题", paperNumber));
+                }
+                // 创建评卷试题
+                List<MarkQuestion> markQuestionList = paperQuestionList.stream().flatMap(e -> {
+                    MarkQuestion markQuestion = new MarkQuestion();
+                    markQuestion.setExamId(examId);
+                    markQuestion.setPaperNumber(paperNumber);
+                    markQuestion.setPaperType(OptionsEnum.A.getCode());
+                    markQuestion.setObjective(false);
+                    markQuestion.setMainNumber(e.getMainNumber());
+                    markQuestion.setSubNumber(e.getSubNumber());
+                    markQuestion.setMainTitle(e.getMainTitle());
+                    markQuestion.setTotalScore(e.getTotalScore());
+                    markQuestion.setIntervalScore(e.getIntervalScore());
+                    return Stream.of(markQuestion);
+                }).collect(Collectors.toList());
+
+
+                // 试卷下分组集合
+                List<Integer> groupNumberList = paperQuestionList.stream()
+                        .map(SubjectiveStructDto::getGroupNumber)
+                        .distinct().sorted()
+                        .collect(Collectors.toList());
+
+                // 评卷员序号
+                AtomicInteger markerNumber = new AtomicInteger(0);
+                for (Integer groupNumber : groupNumberList) {
+                    // 分组下题目集合
+                    List<SubjectiveStructDto> groupQuestionList = paperQuestionList.stream()
+                            .filter(e -> e.getGroupNumber().equals(groupNumber))
+                            .collect(Collectors.toList());
+
+                    String paperNumberGroupNumberNotice = String.format("[试卷编号]%s,[评卷分组(只能用小写数字)]%s",paperNumber, groupNumber);
+
+                    String orgName = null;
+                    Long orgId = null;
+                    BasicCourse basicCourse = basicCourseService.getOne(new QueryWrapper<BasicCourse>().lambda().eq(BasicCourse::getSchoolId,schoolId).eq(BasicCourse::getCode,groupQuestionList.get(0).getCourseCode()));
+                    if (Objects.isNull(basicCourse)){
+                        logicErrorList.add(paperNumberGroupNumberNotice + "的[课程代码]不存在");
+                    } else {
+                        Long teachingRoomId = basicCourse.getTeachingRoomId();
+                        SysOrg sysOrg = sysOrgService.getById(teachingRoomId);
+                        if (Objects.isNull(sysOrg)) {
+                            throw ExceptionResultEnum.ERROR.exception("无法找到机构");
+                        }
+                        orgName = sysOrg.getName();
+                        orgId = sysOrg.getId();
+                    }
+                    Integer markerCount = null;
+                    String markerCodes = null;
+                    List<Integer> markerCountList = groupQuestionList.stream().map(SubjectiveStructDto::getMarkerCount).distinct().collect(Collectors.toList());
+                    if (CollectionUtils.isNotEmpty(markerCountList)){
+                        if (markerCountList.size() > 1){
+                            logicErrorList.add(paperNumberGroupNumberNotice + "的[评卷员数量(系统生成)]不一致");
+                        } else if (markerCountList.size() == 1){
+                            markerCount = markerCountList.get(0);
+                        }
+                    }
+
+
+                    List<String> markerCodesList= groupQuestionList.stream().map(SubjectiveStructDto::getMarkerCodes).distinct().collect(Collectors.toList());
+                    if (CollectionUtils.isNotEmpty(markerCodesList)){
+                        if (markerCodesList.size() > 1){
+                            logicErrorList.add(paperNumberGroupNumberNotice + "的[绑定工号(英文逗号分隔)]不一致");
+                        } else if (markerCodesList.size() == 1){
+                            markerCodes = markerCodesList.get(0);
+                        }
+                    }
+
+                    // 组装评卷员信息
+                    List<MarkUser> markUsers = new ArrayList<>();
+                    if (SystemConstant.strNotNull(markerCodes)) {
+                        // 优先以绑定工号为准
+                        String[] markerCodeArray = markerCodes.split(SystemConstant.COMMA);
+                        for (String markerCode : markerCodeArray) {
+                            SysUser sysUser = sysUserService.getOne(
+                                    new QueryWrapper<SysUser>().lambda().eq(SysUser::getSchoolId, schoolId).eq(SysUser::getLoginName, markerCode).last(SystemConstant.LIMIT1));
+                            if (Objects.isNull(sysUser)) {
+                                logicErrorList.add(String.format("[试卷编号]%s,[评卷分组(只能用小写数字)]%s,[绑定工号(英文逗号分隔)]%s不存在", paperNumber, groupNumber, markerCode));
+                            } else {
+                                // 添加评卷员信息
+                                MarkUser markUser = new MarkUser();
+                                markUser.setUserId(sysUser.getId());
+                                markUser.setLoginName(sysUser.getLoginName());
+                                markUser.setName(sysUser.getRealName());
+                                markUser.setOrgName(orgName);
+                                markUsers.add(markUser);
+                            }
+                        }
+                    } else if (Objects.nonNull(markerCount) && markerCount > 0) {
+                        // 没有绑定工号且评卷员生成数量大于0,生成评卷员(或使用之前工号相同的评卷员)
+                        for (int i = 0; i < markerCount; i++) {
+                            int num = markerNumber.incrementAndGet();
+                            if (num > 99){
+                                logicErrorList.add("[试卷编号]%s的[评卷员数量(系统生成)]总有效数量不能超过99");
+                                break;
+                            }
+                            String markerCode = String.format("%02d",num);
+
+                            SysUser sysUser = sysUserService.getOne(new QueryWrapper<SysUser>().lambda()
+                                    .eq(SysUser::getSchoolId, schoolId)
+                                    .eq(SysUser::getLoginName, markerCode)
+                                    .last(SystemConstant.LIMIT1));
+
+                            MarkUser markUser = new MarkUser();
+                            markUser.setLoginName(markerCode);
+                            markUser.setOrgName(orgName);
+                            markUsers.add(markUser);
+                            if (Objects.nonNull(sysUser)){
+                                markUser.setUserId(sysUser.getId());
+                                markUser.setName(sysUser.getRealName());
+                            } else {
+                                // 创建用户
+                                UserSaveParams userSaveParams = new UserSaveParams();
+                                userSaveParams.setSchoolId(schoolId);
+                                userSaveParams.setLoginName(markerCode);
+                                userSaveParams.setRealName(markerCode);
+                                userSaveParams.setCode(markerCode);
+                                userSaveParams.setPassword(SystemConstant.DEFAULT_PASSWORD);
+                                userSaveParams.setOrgId(orgId);
+                                userSaveParams.setEnable(true);
+                                userSaveParams.setRoleIds(new Long[] { markerRole.getId() });
+                                try {
+                                    markUser.setUserId(sysUserService.saveUser(userSaveParams));
+                                    markUser.setName(markerCode);
+                                } catch (IllegalAccessException e) {
+                                    e.printStackTrace();
+                                }
+                            }
+                        }
+
+                    }
+
+
+                }
+            });
+        }
+        return null;
+    }
+
     /**
      * 验证机构是否存在
      *

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

@@ -151,7 +151,7 @@ public class MarkSettingController {
     @RequestMapping(value = "/subjective_struct/import", method = RequestMethod.POST)
     public Result subjectiveStructImport(@ApiParam(value = "考试ID", required = true) @RequestParam String examId,
             @ApiParam(value = "标答excel文件",required = true) @RequestParam MultipartFile file) throws Exception {
-        Map<String, Object> map = printCommonService.saveTask(file, SystemConstant.convertIdToLong(examId), TaskTypeEnum.OBJECTIVE_STRUCT_IMPORT);
+        Map<String, Object> map = printCommonService.saveTask(file, SystemConstant.convertIdToLong(examId), TaskTypeEnum.SUBJECTIVE_STRUCT_IMPORT);
         map.put("examId", SystemConstant.convertIdToLong(examId));
         return syncObjectiveStructImportService.importTask(map);
     }

BIN
distributed-print/src/main/resources/temps/objectiveStruct.xlsx


BIN
distributed-print/src/main/resources/temps/subjectiveStruct.xlsx


+ 0 - 8
distributed-print/src/test/java/com/qmth/distributed/print/ServiceTest.java

@@ -173,12 +173,4 @@ public class ServiceTest {
         }
         examStudentService.updateBatchById(examStudentList);
     }
-
-    @Test
-    public void testTikuPaperStructAnalyze() {
-        Long examId = 502120560860332032L;
-        String paperNumber = "GDSX-032803";
-        List<PaperStructDimensionResult> result = courseTargetService.findTikuPaperStruct(examId, paperNumber);
-        System.out.println(JSON.toJSONString(result));
-    }
 }

+ 2 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/enums/ImportTemplateEnum.java

@@ -20,6 +20,8 @@ public enum ImportTemplateEnum {
     TEMPLATE_COURSE_DIMENSION("courseDimension.xlsx", "课程知识点导入模板.xlsx"),
     TEMPLATE_MARK_STUDENT("markStudent.xlsx","阅卷数据导入模板.xlsx"),
     TEMPLATE_PAPER_STRUCT("paperStruct.xlsx", "试卷结构导入模板.xlsx"),
+    TEMPLATE_OBJECTIVE_STRUCT("objectiveStruct.xlsx","客观题导入模板.xlsx"),
+    TEMPLATE_SUBJECTIVE_STRUCT("subjectiveStruct.xlsx","主观题导入模板.xlsx"),
 
     STATIC_COURSE_DEGREE_REPORT("course_degree_report.docx", "课程目标达成度");
 

+ 141 - 23
teachcloud-common/src/main/java/com/qmth/teachcloud/common/util/ConvertUtil.java

@@ -23,10 +23,13 @@ import java.awt.image.BufferedImage;
 import java.io.*;
 import java.lang.reflect.Field;
 import java.net.URLEncoder;
-import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 /**
@@ -202,8 +205,13 @@ public class ConvertUtil {
                     ExcelColumn annotation = field.getAnnotation(ExcelColumn.class);
                     if (SystemConstant.strNotNull(v)) {
                         field.setAccessible(true);
-                        field.set(dto, convertAttributeValue(field.getType().getSimpleName(), v));
-                        emptyCell = false;
+                        try {
+                            Object cv = convertAttributeValue(field.getType().getSimpleName(), v);
+                            field.set(dto, cv);
+                            emptyCell = false;
+                        } catch (Exception e) {
+                            excelErrorList.add(String.format("[%s]格式错误: %s", annotation.name(), e.getMessage()));
+                        }
                     } else if (annotation.nullable()) {
                         excelErrorList.add(String.format("[%s]必填", annotation.name()));
                     }
@@ -232,6 +240,11 @@ public class ConvertUtil {
         return excelResult;
     }
 
+    // 定义支持的数据类型枚举
+    private enum DataType {
+        INT, DOUBLE, LONG, FLOAT, DATE, STRING
+    }
+
     /**
      * 将属性的值从String类型转换成他们各自对应的数据类型的值
      *
@@ -239,28 +252,133 @@ public class ConvertUtil {
      * @param value 属性的值
      * @return 对应数据类型的值
      */
-    private static Object convertAttributeValue(String type, String value) throws ParseException {
-        Object result;
-        if ("int".equals(type) || "Integer".equals(type)) {
-            result = Integer.parseInt(value);
-        } else if ("double".equals(type) || "Double".equals(type)) {
-            result = Double.parseDouble(value);
-        } else if ("long".equals(type) || "Long".equals(type)) {
-            result = Long.parseLong(value);
-        } else if ("Date".equals(type)) {
-            SimpleDateFormat sdf;
-            if (value.matches("\\d{4}-\\d{2}-\\d{2}")) {
-                sdf = new SimpleDateFormat("yyyy-MM-dd");
-            } else if (value.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}\\d{2}\\d{2}")) {
-                sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-            } else {
-                return new Date();
-            }
-            return sdf.parse(value);
-        } else {
+    private static Object convertAttributeValue(String type, String value) {
+        // 使用枚举来处理数据类型
+        DataType dataType = getDataType(type);
+
+        switch (dataType) {
+        case INT:
+            return parseInt(value);
+        case DOUBLE:
+            return parseDouble(value);
+        case LONG:
+            return parseLong(value);
+        case FLOAT:
+            return parseFloat(value);
+        case DATE:
+            return parseDate(value);
+        default:
             return value;
         }
-        return result;
+    }
+
+    /**
+     * 获取属性的类型枚举
+     *
+     * @param type 属性类型名称
+     * @return 属性的类型对应的枚举值
+     */
+    private static DataType getDataType(String type) {
+        switch (type.toLowerCase()) {
+        case "int":
+        case "integer":
+            return DataType.INT;
+        case "double":
+            return DataType.DOUBLE;
+        case "long":
+            return DataType.LONG;
+        case "float":
+            return DataType.FLOAT;
+        case "date":
+            return DataType.DATE;
+        case "string":
+            return DataType.STRING;
+        default:
+            return DataType.valueOf(type); // 对于未知类型,尝试直接匹配枚举值
+        }
+    }
+
+    /**
+     * 将字符串转换为整数
+     *
+     * @param value 字符串值
+     * @return 整数值
+     */
+    private static int parseInt(String value) {
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            throw ExceptionResultEnum.EXCEPTION_ERROR.exception("只能用小写整数");
+        }
+    }
+
+    /**
+     * 将字符串转换为双精度浮点数
+     *
+     * @param value 字符串值
+     * @return 双精度浮点数值
+     */
+    private static double parseDouble(String value) {
+        try {
+            return Double.parseDouble(value);
+        } catch (NumberFormatException e) {
+            throw ExceptionResultEnum.ERROR.exception("只能用数字");
+        }
+    }
+
+    /**
+     * 将字符串转换为长整数
+     *
+     * @param value 字符串值
+     * @return 长整数值
+     */
+    private static long parseLong(String value) {
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            throw ExceptionResultEnum.ERROR.exception("只能用数字");
+        }
+    }
+
+    /**
+     * 将字符串转换为长整数
+     *
+     * @param value 字符串值
+     * @return 长整数值
+     */
+    private static float parseFloat(String value) {
+        try {
+            return Float.parseFloat(value);
+        } catch (NumberFormatException e) {
+            throw ExceptionResultEnum.ERROR.exception("只能用数字");
+        }
+    }
+
+    /**
+     * 将字符串转换为日期
+     *
+     * @param value 字符串值
+     * @return 日期值
+     */
+    private static Date parseDate(String value) {
+        // 使用 Java 8 的日期时间 API
+        DateTimeFormatter formatter;
+        Pattern datePattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
+        Pattern datetimePattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}");
+
+        Matcher dateMatcher = datePattern.matcher(value);
+        Matcher datetimeMatcher = datetimePattern.matcher(value);
+
+        if (dateMatcher.matches()) {
+            formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        } else if (datetimeMatcher.matches()) {
+            formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        } else {
+            return new Date();
+        }
+
+        LocalDateTime dateTime = LocalDateTime.parse(value, formatter);
+        return Date.from(dateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
     }
 
     public static void delFolder(String folderPath) {