Browse Source

1.0.5 update

xiaofei 11 tháng trước cách đây
mục cha
commit
6440c485d3
33 tập tin đã thay đổi với 1465 bổ sung412 xóa
  1. 61 0
      paper-library-business/src/main/java/com/qmth/paper/library/business/bean/dto/CodeNameEnableDisabledValue.java
  2. 160 0
      paper-library-business/src/main/java/com/qmth/paper/library/business/bean/dto/ExamStudentImport.java
  3. 29 0
      paper-library-business/src/main/java/com/qmth/paper/library/business/bean/dto/ExamStudentParseDto.java
  4. 0 2
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/ExamCourseService.java
  5. 3 2
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/ExamStudentService.java
  6. 2 0
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/FileUploadService.java
  7. 1 0
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/PaperLibraryCommonService.java
  8. 4 3
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/PaperScanTaskService.java
  9. 0 31
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/ExamCourseServiceImpl.java
  10. 90 98
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/ExamStudentServiceImpl.java
  11. 30 1
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/FileUploadServiceImpl.java
  12. 29 4
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/PaperLibraryCommonServiceImpl.java
  13. 35 29
      paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/PaperScanTaskServiceImpl.java
  14. 173 0
      paper-library-business/src/main/java/com/qmth/paper/library/business/templete/execute/AsyncExamStudentImportService.java
  15. 3 2
      paper-library-business/src/main/java/com/qmth/paper/library/business/templete/execute/AsyncSysOrgImportService.java
  16. 3 2
      paper-library-business/src/main/java/com/qmth/paper/library/business/templete/execute/AsyncSysUserDataImportService.java
  17. 0 92
      paper-library-business/src/main/java/com/qmth/paper/library/business/templete/export/AsyncExportTaskTemplate.java
  18. 9 118
      paper-library-business/src/main/java/com/qmth/paper/library/business/templete/importData/AsyncImportTaskTemplate.java
  19. 2 0
      paper-library-business/src/main/java/com/qmth/paper/library/business/templete/service/TaskLogicService.java
  20. 258 6
      paper-library-business/src/main/java/com/qmth/paper/library/business/templete/service/impl/TaskLogicServiceImpl.java
  21. 33 0
      paper-library-common/src/main/java/com/qmth/paper/library/common/contant/SystemConstant.java
  22. 165 0
      paper-library-common/src/main/java/com/qmth/paper/library/common/entity/BasicField.java
  23. 10 0
      paper-library-common/src/main/java/com/qmth/paper/library/common/entity/ExamStudent.java
  24. 14 14
      paper-library-common/src/main/java/com/qmth/paper/library/common/entity/TBTask.java
  25. 71 0
      paper-library-common/src/main/java/com/qmth/paper/library/common/enums/BasicFieldEnum.java
  26. 4 3
      paper-library-common/src/main/java/com/qmth/paper/library/common/enums/TaskTypeEnum.java
  27. 16 0
      paper-library-common/src/main/java/com/qmth/paper/library/common/mapper/BasicFieldMapper.java
  28. 24 0
      paper-library-common/src/main/java/com/qmth/paper/library/common/service/BasicFieldService.java
  29. 95 0
      paper-library-common/src/main/java/com/qmth/paper/library/common/service/impl/BasicFieldServiceImpl.java
  30. 20 0
      paper-library-common/src/main/resources/mapper/BasicFieldMapper.xml
  31. 45 0
      paper-library/src/main/java/com/qmth/paper/library/api/BasicFieldController.java
  32. 13 5
      paper-library/src/main/java/com/qmth/paper/library/api/ExamStudentController.java
  33. 63 0
      paper-library/src/main/resources/db-log/xf.sql

+ 61 - 0
paper-library-business/src/main/java/com/qmth/paper/library/business/bean/dto/CodeNameEnableDisabledValue.java

@@ -0,0 +1,61 @@
+package com.qmth.paper.library.business.bean.dto;
+
+public class CodeNameEnableDisabledValue {
+
+    private String code;
+    private String name;
+    private Boolean enable;
+    private Boolean disabled;
+    private String value;
+
+    public CodeNameEnableDisabledValue() {
+    }
+
+    public CodeNameEnableDisabledValue(String code, String name, Boolean enable, Boolean disabled, String value) {
+        this.code = code;
+        this.name = name;
+        this.enable = enable;
+        this.disabled = disabled;
+        this.value = value;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+    public Boolean getDisabled() {
+        return disabled;
+    }
+
+    public void setDisabled(Boolean disabled) {
+        this.disabled = disabled;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}

+ 160 - 0
paper-library-business/src/main/java/com/qmth/paper/library/business/bean/dto/ExamStudentImport.java

@@ -0,0 +1,160 @@
+package com.qmth.paper.library.business.bean.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExamStudentImport {
+
+    @ApiModelProperty(value = "姓名")
+    private String studentName;
+    @ApiModelProperty(value = "学号")
+    private String studentCode;
+    @ApiModelProperty(value = "课程代码")
+    private String courseCode;
+    @ApiModelProperty(value = "课程名称")
+    private String courseName;
+    @ApiModelProperty(value = "学院")
+    private String collegeName;
+    @ApiModelProperty(value = "专业")
+    private String majorName;
+    @ApiModelProperty(value = "班级")
+    private String className;
+    @ApiModelProperty(value = "任课教师")
+    private String teacher;
+    @ApiModelProperty(value = "教学班")
+    private String teachClass;
+    @ApiModelProperty("考场")
+    private String examRoom;
+    @ApiModelProperty("成绩")
+    private String score;
+    @ApiModelProperty("备注")
+    private String remark;
+    @ApiModelProperty("必填字段集合")
+    private List<CodeNameEnableDisabledValue> requiredFieldList = new ArrayList<>();
+    @ApiModelProperty("扩展字段集合")
+    private List<CodeNameEnableDisabledValue> extendFieldList = new ArrayList<>();
+    @ApiModelProperty("错误信息")
+    private String errorMsg;
+
+    public String getStudentName() {
+        return studentName;
+    }
+
+    public void setStudentName(String studentName) {
+        this.studentName = studentName;
+    }
+
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+    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 getCollegeName() {
+        return collegeName;
+    }
+
+    public void setCollegeName(String collegeName) {
+        this.collegeName = collegeName;
+    }
+
+    public String getMajorName() {
+        return majorName;
+    }
+
+    public void setMajorName(String majorName) {
+        this.majorName = majorName;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public void setClassName(String className) {
+        this.className = className;
+    }
+
+    public String getTeacher() {
+        return teacher;
+    }
+
+    public void setTeacher(String teacher) {
+        this.teacher = teacher;
+    }
+
+    public String getTeachClass() {
+        return teachClass;
+    }
+
+    public void setTeachClass(String teachClass) {
+        this.teachClass = teachClass;
+    }
+
+    public String getExamRoom() {
+        return examRoom;
+    }
+
+    public void setExamRoom(String examRoom) {
+        this.examRoom = examRoom;
+    }
+
+    public String getScore() {
+        return score;
+    }
+
+    public void setScore(String score) {
+        this.score = score;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public List<CodeNameEnableDisabledValue> getRequiredFieldList() {
+        return requiredFieldList;
+    }
+
+    public void setRequiredFieldList(List<CodeNameEnableDisabledValue> requiredFieldList) {
+        this.requiredFieldList = requiredFieldList;
+    }
+
+    public List<CodeNameEnableDisabledValue> getExtendFieldList() {
+        return extendFieldList;
+    }
+
+    public void setExtendFieldList(List<CodeNameEnableDisabledValue> extendFieldList) {
+        this.extendFieldList = extendFieldList;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+}

+ 29 - 0
paper-library-business/src/main/java/com/qmth/paper/library/business/bean/dto/ExamStudentParseDto.java

@@ -0,0 +1,29 @@
+package com.qmth.paper.library.business.bean.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.List;
+
+public class ExamStudentParseDto {
+
+    @ApiModelProperty(value = "表头")
+    private String[] columnNames;
+    @ApiModelProperty(value = "解析数据集合")
+    private List<ExamStudentImport> basicExamStudentImportList;
+
+    public String[] getColumnNames() {
+        return columnNames;
+    }
+
+    public void setColumnNames(String[] columnNames) {
+        this.columnNames = columnNames;
+    }
+
+    public List<ExamStudentImport> getBasicExamStudentImportList() {
+        return basicExamStudentImportList;
+    }
+
+    public void setBasicExamStudentImportList(List<ExamStudentImport> basicExamStudentImportList) {
+        this.basicExamStudentImportList = basicExamStudentImportList;
+    }
+}

+ 0 - 2
paper-library-business/src/main/java/com/qmth/paper/library/business/service/ExamCourseService.java

@@ -17,8 +17,6 @@ import java.util.List;
  */
 public interface ExamCourseService extends IMppService<ExamCourse> {
 
-    List<ExamCourse> saveBatchStudentCourse(Long examId, List<ExamStudent> basicStudentList);
-
     void removeByExamIdAndCourseCode(Long examId, String courseCode);
 
     List<SelectResult> listConditionByExamId(Long examId);

+ 3 - 2
paper-library-business/src/main/java/com/qmth/paper/library/business/service/ExamStudentService.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.qmth.paper.library.common.entity.ExamStudent;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletResponse;
 import java.util.List;
 
 /**
@@ -40,7 +41,7 @@ public interface ExamStudentService extends IService<ExamStudent> {
 
     void updateBindCount(Long studentId);
 
-    void removeByExamIdAndCourseCode(Long examId, String courseCode);
-
     int countByExamIdAndCourseCode(Long examId, String courseCode);
+
+    void downloadTemplate(HttpServletResponse response);
 }

+ 2 - 0
paper-library-business/src/main/java/com/qmth/paper/library/business/service/FileUploadService.java

@@ -10,6 +10,8 @@ import java.io.InputStream;
 
 public interface FileUploadService {
 
+    FilePathVo uploadFile(File sourceFile, UploadFileEnum uploadFileEnum, String fileName);
+
     FilePathVo uploadFile(MultipartFile file, UploadFileEnum uploadFileEnum, String filePathName);
 
     File downloadFile(String path, String filePathName) throws Exception;

+ 1 - 0
paper-library-business/src/main/java/com/qmth/paper/library/business/service/PaperLibraryCommonService.java

@@ -44,6 +44,7 @@ public interface PaperLibraryCommonService {
      * 保存任务
      */
     Map<String, Object> saveTask(MultipartFile file, TaskTypeEnum taskTypeEnum);
+    Map<String, Object> saveTask(MultipartFile file, Long examId, TaskTypeEnum taskTypeEnum);
 
     PathSequenceVo saveLibraryFile(PaperScanTask paperScanTask, ExamStudent examStudent, UploadFileEnum uploadFileEnum, MultipartFile... files);
 

+ 4 - 3
paper-library-business/src/main/java/com/qmth/paper/library/business/service/PaperScanTaskService.java

@@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.qmth.paper.library.business.bean.result.PaperScanTaskDetailResult;
 import com.qmth.paper.library.business.bean.result.PaperScanTaskResult;
-import com.qmth.paper.library.business.entity.ExamCourse;
 import com.qmth.paper.library.business.entity.PaperScanTask;
 import com.qmth.paper.library.common.entity.ExamStudent;
 import com.qmth.paper.library.common.enums.RecognitionTypeEnum;
@@ -34,11 +33,13 @@ public interface PaperScanTaskService extends IService<PaperScanTask> {
      */
     void clearScanData(Long paperScanTaskId);
 
-    void addBatchScanTask(Long examId, RecognitionTypeEnum recognitionType, StoreTypeEnum storeType, List<ExamCourse> examCourses, List<ExamStudent> basicStudentList);
-
     void addScanTask(Long examId, RecognitionTypeEnum recognitionType, StoreTypeEnum storeType, ExamStudent examStudent);
 
     PaperScanTask getByExamIdAndCourseCode(Long examId, String courseCode);
 
     void updateStudentCount(PaperScanTask paperScanTask);
+
+    void updatePaperScanTask(Long examId, RecognitionTypeEnum recognitionType, StoreTypeEnum storeType, List<ExamStudent> examStudentList, Long createId);
+
+    List<PaperScanTask> listByExamId(Long examId);
 }

+ 0 - 31
paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/ExamCourseServiceImpl.java

@@ -2,27 +2,18 @@ package com.qmth.paper.library.business.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.github.jeffreyning.mybatisplus.service.MppServiceImpl;
 import com.qmth.paper.library.business.bean.result.SelectResult;
 import com.qmth.paper.library.business.entity.ExamCourse;
-import com.qmth.paper.library.business.entity.PaperLibrary;
-import com.qmth.paper.library.business.entity.PaperScanTask;
 import com.qmth.paper.library.business.mapper.ExamCourseMapper;
 import com.qmth.paper.library.business.service.ExamCourseService;
-import com.qmth.paper.library.business.service.PaperLibraryService;
-import com.qmth.paper.library.business.service.PaperScanTaskService;
 import com.qmth.paper.library.common.entity.ExamStudent;
 import com.qmth.paper.library.common.enums.ExceptionResultEnum;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
-import javax.annotation.Resource;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -35,28 +26,6 @@ import java.util.stream.Collectors;
 @Service
 public class ExamCourseServiceImpl extends MppServiceImpl<ExamCourseMapper, ExamCourse> implements ExamCourseService {
 
-    @Resource
-    private PaperScanTaskService paperScanTaskService;
-    @Resource
-    private PaperLibraryService paperLibraryService;
-
-    @Transactional
-    @Override
-    public List<ExamCourse> saveBatchStudentCourse(Long examId, List<ExamStudent> basicStudentList) {
-        Map<String, String> stringMap = basicStudentList.stream().collect(Collectors.toMap(ExamStudent::getCourseCode, ExamStudent::getCourseName, (oldValue, newValue) -> oldValue));
-        List<ExamCourse> examCourses = new ArrayList<>();
-        for (Map.Entry<String, String> entry : stringMap.entrySet()) {
-            PaperScanTask paperScanTask = paperScanTaskService.getByExamIdAndCourseCode(examId, entry.getKey());
-            // 校验是否已扫描
-            if (paperScanTask != null && paperLibraryService.countByPaperScanTaskId(paperScanTask.getId()) > 0) {
-                throw ExceptionResultEnum.ERROR.exception(String.format("课程[%s(%s)]已开始扫描,导入失败。请清除扫描数据后重试", entry.getValue(), entry.getKey()));
-            }
-            examCourses.add(new ExamCourse(examId, entry.getKey(), entry.getValue()));
-        }
-        this.saveOrUpdateBatchByMultiId(examCourses);
-        return examCourses;
-    }
-
     @Override
     public void removeByExamIdAndCourseCode(Long examId, String courseCode) {
         QueryWrapper<ExamCourse> queryWrapper = new QueryWrapper<>();

+ 90 - 98
paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/ExamStudentServiceImpl.java

@@ -5,34 +5,41 @@ 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.boot.tools.excel.ExcelReader;
+import com.qmth.boot.tools.excel.ExcelWriter;
 import com.qmth.boot.tools.excel.enums.ExcelType;
-import com.qmth.paper.library.business.entity.ExamCourse;
 import com.qmth.paper.library.business.entity.PaperScanTask;
 import com.qmth.paper.library.business.mapper.ExamStudentMapper;
-import com.qmth.paper.library.business.service.ExamCourseService;
-import com.qmth.paper.library.business.service.ExamStudentService;
-import com.qmth.paper.library.business.service.PaperLibraryService;
-import com.qmth.paper.library.business.service.PaperScanTaskService;
+import com.qmth.paper.library.business.service.*;
+import com.qmth.paper.library.business.templete.execute.AsyncExamStudentImportService;
 import com.qmth.paper.library.common.bean.dto.syssetting.SimpleObject;
 import com.qmth.paper.library.common.contant.SysSettingConstant;
 import com.qmth.paper.library.common.contant.SystemConstant;
+import com.qmth.paper.library.common.entity.BasicField;
 import com.qmth.paper.library.common.entity.ExamStudent;
 import com.qmth.paper.library.common.entity.SysUser;
-import com.qmth.paper.library.common.enums.ExceptionResultEnum;
-import com.qmth.paper.library.common.enums.RecognitionTypeEnum;
-import com.qmth.paper.library.common.enums.StoreTypeEnum;
+import com.qmth.paper.library.common.enums.*;
+import com.qmth.paper.library.common.service.BasicFieldService;
 import com.qmth.paper.library.common.service.CommonCacheService;
 import com.qmth.paper.library.common.util.ServletUtil;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.IndexedColors;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
-import java.io.IOException;
-import java.util.*;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -45,6 +52,9 @@ import java.util.stream.Collectors;
  */
 @Service
 public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentMapper, ExamStudent> implements ExamStudentService {
+
+    @Resource
+    private BasicFieldService basicFieldService;
     @Resource
     private ExamCourseService examCourseService;
     @Resource
@@ -53,6 +63,10 @@ public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentMapper, ExamS
     private PaperScanTaskService paperScanTaskService;
     @Resource
     private CommonCacheService commonCacheService;
+    @Resource
+    private PaperLibraryCommonService paperLibraryCommonService;
+    @Resource
+    private AsyncExamStudentImportService asyncExamStudentImportService;
 
     @Override
     public IPage<ExamStudent> findStudentPage(Long examId, String courseCode, String collegeName, String majorName, String clazzName, String queryParams, int pageNumber, int pageSize) {
@@ -199,23 +213,21 @@ public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentMapper, ExamS
         if (StringUtils.isBlank(storeTypeSimpleObject.getValue())) {
             throw ExceptionResultEnum.ERROR.exception("文件存储方式未设置");
         }
+        List<BasicField> basicFieldList = basicFieldService.listBySchoolId(sysUser.getSchoolId());
+        if (CollectionUtils.isEmpty(basicFieldList)) {
+            throw ExceptionResultEnum.ERROR.exception("字段管理未配置");
+        }
+
         RecognitionTypeEnum recognitionType = RecognitionTypeEnum.valueOf(recognitionTypeSimpleObject.getValue());
         StoreTypeEnum storeType = StoreTypeEnum.valueOf(storeTypeSimpleObject.getValue());
 
         try {
-            ExcelReader excelReader = ExcelReader.create(ExcelType.XLSX, file.getInputStream(), 0);
-            List<ExamStudent> basicStudentList = excelReader.getObjectList(ExamStudent.class);
-            // 校验excel内容
-            validStudentData(semesterId, examId, basicStudentList, storeType, sysUser);
-            // 保存课程
-            List<ExamCourse> examCourses = examCourseService.saveBatchStudentCourse(examId, basicStudentList);
-            for (ExamCourse examCourse : examCourses) {
-                this.removeByExamIdAndCourseCode(examId, examCourse.getCourseCode());
-            }
-            // 保存学生
-            this.saveBatch(basicStudentList);
-            // 新建任务
-            paperScanTaskService.addBatchScanTask(examId, recognitionType, storeType, examCourses, basicStudentList);
+            Map<String, Object> map = paperLibraryCommonService.saveTask(file, examId, TaskTypeEnum.EXAM_STUDENT_IMPORT);
+            map.put("examId", examId);
+            map.put("recognitionType", recognitionType);
+            map.put("storeType", storeType);
+            map.put("inputStream", file.getInputStream());
+            asyncExamStudentImportService.importTask(map);
         } catch (Exception e) {
             throw ExceptionResultEnum.ERROR.exception(e.getMessage());
         }
@@ -229,14 +241,6 @@ public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentMapper, ExamS
         this.update(updateWrapper);
     }
 
-    @Override
-    public void removeByExamIdAndCourseCode(Long examId, String courseCode) {
-        QueryWrapper<ExamStudent> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda().eq(ExamStudent::getExamId, examId)
-                .eq(ExamStudent::getCourseCode, courseCode);
-        this.remove(queryWrapper);
-    }
-
     @Override
     public int countByExamIdAndCourseCode(Long examId, String courseCode) {
         QueryWrapper<ExamStudent> queryWrapper = new QueryWrapper<>();
@@ -245,80 +249,68 @@ public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentMapper, ExamS
         return this.count(queryWrapper);
     }
 
-    /**
-     * 学生保存公共方法
-     */
-    private void validStudentData(Long semesterId, Long examId, List<ExamStudent> basicStudentList, StoreTypeEnum storeType, SysUser sysUser) {
-        Map<String, String> courseMap = new HashMap<>();
-        Map<String, List<String>> studentCodeMap = new HashMap<>();
-        for (ExamStudent basicStudent : basicStudentList) {
-            // 学号去掉空格
-            basicStudent.setStudentCode(StringUtils.isNotBlank(basicStudent.getStudentCode()) ? StringUtils.deleteWhitespace(basicStudent.getStudentCode()) : null);
-            // 课程代码去掉空格
-            basicStudent.setCourseCode(StringUtils.isNotBlank(basicStudent.getCourseCode()) ? StringUtils.deleteWhitespace(basicStudent.getCourseCode()) : null);
-            // 课程名称去掉空格
-            basicStudent.setCourseName(StringUtils.isNotBlank(basicStudent.getCourseName()) ? StringUtils.deleteWhitespace(basicStudent.getCourseName()) : null);
-            // 考场去掉空格
-            basicStudent.setExamRoom(StringUtils.isNotBlank(basicStudent.getExamRoom()) ? StringUtils.deleteWhitespace(basicStudent.getExamRoom()) : null);
-            // 班级去掉空格
-            basicStudent.setClassName(StringUtils.isNotBlank(basicStudent.getClassName()) ? StringUtils.deleteWhitespace(basicStudent.getClassName()) : null);
+    @Override
+    public void downloadTemplate(HttpServletResponse response) {
+        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
+        List<BasicField> basicFieldList = basicFieldService.listBySchoolId(schoolId);
+        if (CollectionUtils.isEmpty(basicFieldList)) {
+            throw ExceptionResultEnum.ERROR.exception("字段管理未设置");
+        }
 
+        List<String> columnNameList = basicFieldList.stream().filter(m -> m.getEnable()).map(BasicField::getName).collect(Collectors.toList());
+        try {
+            String sheetName = "考生导入模板";
+            log.debug("导出Excel开始...");
+            response.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode("考生导入模板", SystemConstant.CHARSET_NAME) + ".xlsx");
+            response.setContentType("application/vnd.ms-excel");
+            ServletOutputStream outputStream = response.getOutputStream();
+            ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
+            writer.writeDataArrays(sheetName, null, columnNameList.toArray(new String[columnNameList.size()]), null);
 
-            StringJoiner errorMsg = new StringJoiner(";");
-            if (StringUtils.isBlank(basicStudent.getStudentCode())) {
-                errorMsg.add("学号必填");
-            }
-            if (StringUtils.isBlank(basicStudent.getStudentName())) {
-                errorMsg.add("姓名必填");
-            }
-            if (StringUtils.isBlank(basicStudent.getCourseCode())) {
-                errorMsg.add("课程代码必填");
-            }
-            if (StringUtils.isBlank(basicStudent.getCourseName())) {
-                errorMsg.add("课程名称必填");
-            }
-            if (StoreTypeEnum.ROOM.equals(storeType) && StringUtils.isBlank(basicStudent.getExamRoom())) {
-                errorMsg.add("考场必填");
-            }
-            if (StoreTypeEnum.CLASS.equals(storeType) && StringUtils.isBlank(basicStudent.getClassName())) {
-                errorMsg.add("班级必填");
-            }
-            if (courseMap.containsKey(basicStudent.getCourseCode())) {
-                if (!basicStudent.getCourseName().equals(courseMap.get(basicStudent.getCourseCode()))) {
-                    errorMsg.add("课程代码[" + basicStudent.getCourseCode() + "]存在多个课程名称[" + courseMap.get(basicStudent.getCourseCode()) + "," + basicStudent.getCourseName() + "]");
-                }
-            } else {
-                courseMap.put(basicStudent.getCourseCode(), basicStudent.getCourseName());
-            }
-            // 考生在同课程下重复
-            if (studentCodeMap.containsKey(basicStudent.getCourseCode())) {
-                List<String> studentCodeList = studentCodeMap.get(basicStudent.getCourseCode());
-                if (CollectionUtils.isEmpty(studentCodeList)) {
-                    studentCodeMap.put(basicStudent.getCourseCode(), new ArrayList<>());
+            // 必填字段
+            List<String> requireFields = Arrays.asList(new String[]{BasicFieldEnum.STUDENT_CODE.getName(), BasicFieldEnum.STUDENT_NAME.getName(), BasicFieldEnum.COURSE_CODE.getName(), BasicFieldEnum.CLASS_NAME.getName()});
+
+            List<Integer> columnNameIndexList = new ArrayList<>();
+            List<Integer> columnNameRequireIndexList = new ArrayList<>();
+            for (int i = 0; i < columnNameList.size(); i++) {
+                if (requireFields.contains(columnNameList.get(i))) {
+                    columnNameRequireIndexList.add(i);
                 } else {
-                    if (studentCodeList.contains(basicStudent.getStudentCode())) {
-                        errorMsg.add("学号[" + basicStudent.getStudentCode() + "]在课程代码[" + basicStudent.getCourseCode() + "]下已存在");
-                    } else {
-                        studentCodeList.add(basicStudent.getStudentCode());
-                        studentCodeMap.put(basicStudent.getCourseCode(), studentCodeList);
-                    }
+                    columnNameIndexList.add(i);
                 }
-            } else {
-                List<String> studentCodeList = new ArrayList<>();
-                studentCodeList.add(basicStudent.getStudentCode());
-                studentCodeMap.put(basicStudent.getCourseCode(), studentCodeList);
             }
 
-            if (StringUtils.isNotBlank(errorMsg.toString())) {
-                throw ExceptionResultEnum.ERROR.exception(errorMsg.toString());
+            // 表头背景灰色
+            if (CollectionUtils.isNotEmpty(columnNameIndexList)) {
+                CellStyle cellStyle1 = writer.createCellStyle();
+                Font font1 = writer.createFont();
+                font1.setBold(true);
+                font1.setFontHeightInPoints((short) 15);
+                cellStyle1.setFont(font1);
+                cellStyle1.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+                cellStyle1.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+                writer.setCellStyle(sheetName, cellStyle1, 0, ArrayUtils.toPrimitive(columnNameIndexList.toArray(new Integer[columnNameIndexList.size()])));
             }
 
-            basicStudent.setId(SystemConstant.getDbUuid());
-            basicStudent.setSchoolId(sysUser.getSchoolId());
-            basicStudent.setSemesterId(semesterId);
-            basicStudent.setExamId(examId);
-            basicStudent.setCreateId(sysUser.getId());
-            basicStudent.setCreateTime(System.currentTimeMillis());
+            // 必填字段背景灰色、字段红色
+            if (CollectionUtils.isNotEmpty(columnNameRequireIndexList)) {
+                CellStyle cellStyle2 = writer.createCellStyle();
+                Font font2 = writer.createFont();
+                font2.setBold(true);
+                font2.setFontHeightInPoints((short) 15);
+                font2.setColor(IndexedColors.RED.getIndex());
+                cellStyle2.setFont(font2);
+                cellStyle2.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+                cellStyle2.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+                writer.setCellStyle(sheetName, cellStyle2, 0, ArrayUtils.toPrimitive(columnNameRequireIndexList.toArray(new Integer[columnNameRequireIndexList.size()])));
+            }
+            writer.output(outputStream);
+            outputStream.flush();
+            outputStream.close();
+            log.debug("导出Excel结束");
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception("考生导入模板导出失败:" + e.getMessage());
         }
+
     }
 }

+ 30 - 1
paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/FileUploadServiceImpl.java

@@ -13,6 +13,7 @@ import com.qmth.paper.library.common.util.FileStoreUtil;
 import com.qmth.paper.library.common.util.ResultUtil;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,6 +24,7 @@ import javax.annotation.Resource;
 import java.io.*;
 import java.nio.file.Files;
 import java.util.Objects;
+import java.util.StringJoiner;
 
 /**
  * 文件上传服务类
@@ -36,6 +38,33 @@ public class FileUploadServiceImpl implements FileUploadService {
     @Resource
     CommonCacheService commonCacheService;
 
+    /**
+     * 上传文件
+     *
+     * @param sourceFile     上传文件
+     * @param uploadFileEnum 文件类型
+     * @param fileName       文件名称
+     */
+    @Override
+    public FilePathVo uploadFile(File sourceFile, UploadFileEnum uploadFileEnum, String fileName) {
+        try {
+            String rootPath = fileStoreUtil.buildPath(UploadFileEnum.FILE, true);
+            String dirName = rootPath + File.separator + fileName;
+            String md5 = DigestUtils.md5Hex(new FileInputStream(sourceFile));
+            fileStoreUtil.fileUpload(dirName, new FileInputStream(sourceFile), md5);
+            String type = fileStoreUtil.isOssStore() ? SystemConstant.OSS : SystemConstant.LOCAL;
+            return new FilePathVo(dirName, uploadFileEnum, type, md5);
+        } catch (Exception e) {
+            log.error(SystemConstant.LOG_ERROR, e);
+            if (e instanceof ApiException) {
+                ResultUtil.error((ApiException) e, e.getMessage());
+            } else {
+                ResultUtil.error(e.getMessage());
+            }
+        }
+        return null;
+    }
+
     /**
      * @param file
      * @param filePathName
@@ -111,7 +140,7 @@ public class FileUploadServiceImpl implements FileUploadService {
 
     @Override
     public boolean copy(String sourcePath, String destPath) {
-        if(StringUtils.isNotBlank(sourcePath) && StringUtils.isNotBlank(destPath)){
+        if (StringUtils.isNotBlank(sourcePath) && StringUtils.isNotBlank(destPath)) {
             return fileStoreUtil.copy(sourcePath, destPath);
         } else {
             return false;

+ 29 - 4
paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/PaperLibraryCommonServiceImpl.java

@@ -18,16 +18,14 @@ import com.qmth.paper.library.common.config.DictionaryConfig;
 import com.qmth.paper.library.common.contant.SpringContextHolder;
 import com.qmth.paper.library.common.contant.SystemConstant;
 import com.qmth.paper.library.common.entity.*;
-import com.qmth.paper.library.common.enums.ExceptionResultEnum;
-import com.qmth.paper.library.common.enums.StoreTypeEnum;
-import com.qmth.paper.library.common.enums.TaskTypeEnum;
-import com.qmth.paper.library.common.enums.UploadFileEnum;
+import com.qmth.paper.library.common.enums.*;
 import com.qmth.paper.library.common.service.*;
 import com.qmth.paper.library.common.util.FileStoreUtil;
 import com.qmth.paper.library.common.util.ResultUtil;
 import com.qmth.paper.library.common.util.ServletUtil;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -262,6 +260,33 @@ public class PaperLibraryCommonServiceImpl implements PaperLibraryCommonService
         return map;
     }
 
+    @Override
+    @Transactional
+    public Map<String, Object> saveTask(MultipartFile file, Long examId, TaskTypeEnum taskTypeEnum) {
+        Map<String, Object> map = new HashMap<>();
+        try {
+            String rootPath = fileStoreUtil.buildPath(UploadFileEnum.FILE, true);
+            String dirName = rootPath + File.separator + SystemConstant.getUuid() + "." + FilenameUtils.getExtension(file.getOriginalFilename());
+            FilePathVo filePathVo = fileUploadService.uploadFile(file, UploadFileEnum.FILE, dirName);
+            SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
+            TBTask tbTask = new TBTask(taskTypeEnum, TaskStatusEnum.INIT, file.getOriginalFilename(), JSON.toJSONString(filePathVo), requestUser.getId(), requestUser.getSchoolId());
+            tbTask.setExamId(examId);
+            tbTaskService.save(tbTask);
+
+            map.putIfAbsent(SystemConstant.TASK, tbTask);
+            map.putIfAbsent(SystemConstant.USER, requestUser);
+            map.computeIfAbsent(SystemConstant.TB_TASK_ID, v -> tbTask.getId());
+        } catch (Exception e) {
+            log.error(SystemConstant.LOG_ERROR, e);
+            if (e instanceof ApiException) {
+                ResultUtil.error((ApiException) e, e.getMessage());
+            } else {
+                ResultUtil.error(e.getMessage());
+            }
+        }
+        return map;
+    }
+
     @Override
     public PathSequenceVo saveLibraryFile(PaperScanTask paperScanTask, ExamStudent examStudent, UploadFileEnum uploadFileEnum, MultipartFile... files) {
         BasicSchool basicSchool = basicSchoolService.getById(paperScanTask.getSchoolId());

+ 35 - 29
paper-library-business/src/main/java/com/qmth/paper/library/business/service/impl/PaperScanTaskServiceImpl.java

@@ -9,7 +9,6 @@ import com.qmth.paper.library.business.bean.result.PaperScanTaskDetailResult;
 import com.qmth.paper.library.business.bean.result.PaperScanTaskResult;
 import com.qmth.paper.library.business.entity.ExamCourse;
 import com.qmth.paper.library.business.entity.PaperLibrary;
-import com.qmth.paper.library.business.entity.PaperLibraryOther;
 import com.qmth.paper.library.business.entity.PaperScanTask;
 import com.qmth.paper.library.business.mapper.PaperScanTaskMapper;
 import com.qmth.paper.library.business.service.*;
@@ -27,12 +26,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import javax.management.relation.RoleInfoNotFoundException;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -114,28 +108,6 @@ public class PaperScanTaskServiceImpl extends ServiceImpl<PaperScanTaskMapper, P
 
     }
 
-    @Transactional
-    @Override
-    public void addBatchScanTask(Long examId, RecognitionTypeEnum recognitionType, StoreTypeEnum storeType, List<ExamCourse> examCourses, List<ExamStudent> basicStudentList) {
-        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
-        List<PaperScanTask> paperScanTaskList = new ArrayList<>();
-        for (ExamCourse examCourse : examCourses) {
-            int studentCount = Math.toIntExact(basicStudentList.stream().filter(m -> m.getCourseCode().equals(examCourse.getCourseCode())).count());
-            PaperScanTask paperScanTask = this.getByExamIdAndCourseCode(examId, examCourse.getCourseCode());
-            if (paperScanTask == null) {
-                String scanTaskName = String.format("%s(%s)", examCourse.getCourseName(), examCourse.getCourseCode());
-                String scanTaskCode = paperLibraryCommonService.getScanTaskCode(schoolId);
-                paperScanTask = new PaperScanTask(schoolId, examId, scanTaskCode, scanTaskName, examCourse.getCourseCode(), examCourse.getCourseName(), recognitionType, storeType, studentCount);
-            } else {
-                paperScanTask.setRecognitionType(recognitionType);
-                paperScanTask.setStoreType(storeType);
-                paperScanTask.setStudentCount(studentCount);
-            }
-            paperScanTaskList.add(paperScanTask);
-        }
-        this.saveOrUpdateBatch(paperScanTaskList);
-    }
-
     @Override
     public void addScanTask(Long examId, RecognitionTypeEnum recognitionType, StoreTypeEnum storeType, ExamStudent examStudent) {
         Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
@@ -170,4 +142,38 @@ public class PaperScanTaskServiceImpl extends ServiceImpl<PaperScanTaskMapper, P
         this.update(updateWrapper);
     }
 
+    @Transactional
+    @Override
+    public void updatePaperScanTask(Long examId, RecognitionTypeEnum recognitionType, StoreTypeEnum storeType, List<ExamStudent> examStudentList, Long createId) {
+        Map<String, String> stringMap = examStudentList.stream().collect(Collectors.toMap(ExamStudent::getCourseCode, ExamStudent::getCourseName, (oldValue, newValue) -> oldValue));
+        List<ExamCourse> examCourses = new ArrayList<>();
+        for (Map.Entry<String, String> entry : stringMap.entrySet()) {
+            examCourses.add(new ExamCourse(examId, entry.getKey(), entry.getValue()));
+        }
+        examCourseService.saveOrUpdateBatchByMultiId(examCourses);
+
+        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
+        List<PaperScanTask> paperScanTaskList = new ArrayList<>();
+        for (ExamCourse examCourse : examCourses) {
+            int studentCount = Math.toIntExact(examStudentList.stream().filter(m -> m.getCourseCode().equals(examCourse.getCourseCode())).count());
+            PaperScanTask paperScanTask = this.getByExamIdAndCourseCode(examId, examCourse.getCourseCode());
+            if (paperScanTask == null) {
+                String scanTaskName = String.format("%s(%s)", examCourse.getCourseName(), examCourse.getCourseCode());
+                String scanTaskCode = paperLibraryCommonService.getScanTaskCode(schoolId);
+                paperScanTask = new PaperScanTask(schoolId, examId, scanTaskCode, scanTaskName, examCourse.getCourseCode(), examCourse.getCourseName(), recognitionType, storeType, studentCount);
+            } else {
+                paperScanTask.setStudentCount(studentCount);
+            }
+            paperScanTaskList.add(paperScanTask);
+        }
+        this.saveOrUpdateBatch(paperScanTaskList);
+    }
+
+    @Override
+    public List<PaperScanTask> listByExamId(Long examId) {
+        QueryWrapper<PaperScanTask> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(PaperScanTask::getExamId, examId);
+        return this.list(queryWrapper);
+    }
+
 }

+ 173 - 0
paper-library-business/src/main/java/com/qmth/paper/library/business/templete/execute/AsyncExamStudentImportService.java

@@ -0,0 +1,173 @@
+package com.qmth.paper.library.business.templete.execute;
+
+import cn.hutool.core.date.DateUtil;
+import com.alibaba.fastjson.JSON;
+import com.qmth.boot.tools.excel.ExcelWriter;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.paper.library.business.bean.dto.CodeNameEnableDisabledValue;
+import com.qmth.paper.library.business.bean.dto.ExamStudentImport;
+import com.qmth.paper.library.business.bean.vo.FilePathVo;
+import com.qmth.paper.library.business.service.ExamStudentService;
+import com.qmth.paper.library.business.service.FileUploadService;
+import com.qmth.paper.library.business.service.PaperScanTaskService;
+import com.qmth.paper.library.business.templete.importData.AsyncImportTaskTemplate;
+import com.qmth.paper.library.business.templete.service.TaskLogicService;
+import com.qmth.paper.library.common.contant.SpringContextHolder;
+import com.qmth.paper.library.common.contant.SystemConstant;
+import com.qmth.paper.library.common.entity.ExamStudent;
+import com.qmth.paper.library.common.entity.TBTask;
+import com.qmth.paper.library.common.enums.*;
+import com.qmth.paper.library.common.service.TBTaskService;
+import com.qmth.paper.library.common.util.Result;
+import com.qmth.paper.library.common.util.ResultUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.text.MessageFormat;
+import java.util.*;
+
+/**
+ * @Description: 考生字典导入同步任务
+ * @Author: CaoZixuan
+ * @Date: 2024-02-26
+ */
+@Service
+public class AsyncExamStudentImportService extends AsyncImportTaskTemplate {
+
+    private final static Logger log = LoggerFactory.getLogger(AsyncExamStudentImportService.class);
+
+    public static final String BEGIN_TITLE = "->开始处理导入文件";
+    public static final String EXCEPTION_TITLE = "->数据处理发生异常!";
+    public static final String EXCEPTION_DATA = "错误信息:";
+    public static final String FINISH_TITLE = "->数据处理结束,共处理了";
+    public static final String FINISH_TOTAL_SIZE = "条数据,成功";
+    public static final String FINISH_SUCCESS_SIZE = "条数据,失败";
+    public static final String FINISH_ERROR_SIZE = "条数据";
+
+    @Resource
+    private PaperScanTaskService paperScanTaskService;
+    @Resource
+    private ExamStudentService examStudentService;
+    @Resource
+    private FileUploadService fileUploadService;
+
+    @Override
+    public Result importTask(Map<String, Object> map) {
+        TBTask tbTask = (TBTask) map.get(SystemConstant.TASK);
+
+        StringJoiner stringJoinerSummary = new StringJoiner("\n");
+        SystemConstant.addSummary(stringJoinerSummary, BEGIN_TITLE);
+        tbTask.setStatus(TaskStatusEnum.RUNNING);
+        TBTaskService tbTaskService = SpringContextHolder.getBean(TBTaskService.class);
+        tbTaskService.updateById(tbTask);
+        int dataCount;
+        int successCount;
+        int errorCount;
+        boolean hasError;
+        List<ExamStudentImport> errorDataList;
+
+        try {
+            TaskLogicService taskLogicService = SpringContextHolder.getBean(TaskLogicService.class);
+
+            // 执行导入考务数据
+            Map<String, Object> result = taskLogicService.executeImportExamStudentLogic(map, stringJoinerSummary);
+            dataCount = Integer.parseInt(String.valueOf(result.get(SystemConstant.DATA_COUNT)));
+            successCount = Integer.parseInt(String.valueOf(result.get(SystemConstant.SUCCESS_DATA_COUNT)));
+            errorCount = Integer.parseInt(String.valueOf(result.get(SystemConstant.ERROR_DATA_COUNT)));
+            hasError = Boolean.parseBoolean(String.valueOf(result.get(SystemConstant.HAS_ERROR_DATA)));
+
+            if (hasError) {
+                SystemConstant.addSummary(stringJoinerSummary, "有异常数据,开始生成错误文件");
+                tbTask.setResult(TaskResultEnum.ERROR);
+                // 有异常数据
+                errorDataList = JSON.parseArray(JSON.toJSONString(result.get(SystemConstant.ERROR_DATA_LIST)), ExamStudentImport.class);
+                try {
+                    String[] columnNames = (String[]) result.get(SystemConstant.COLUMN_NAMES);
+                    File excelFileTemp = SystemConstant.getFileTempVar(SystemConstant.EXCEL_PREFIX);
+                    this.createErrorExcelFile(columnNames, errorDataList, excelFileTemp);
+                    String fileName = SystemConstant.getNanoId() + SystemConstant.EXCEL_PREFIX;
+
+                    FilePathVo filePathVo = fileUploadService.uploadFile(excelFileTemp, UploadFileEnum.FILE, fileName);
+                    tbTask.setErrorFilePath(JSON.toJSONString(filePathVo));
+                    SystemConstant.addSummary(stringJoinerSummary, "错误文件生成生成,请到\"导入结果查询\"中,下载错误文件");
+                } catch (Exception e) {
+                    SystemConstant.addSummary(stringJoinerSummary, "错误文件生成失败," + e.getMessage());
+                }
+            } else {
+                SystemConstant.addSummary(stringJoinerSummary, "数据校验通过,开始写入考生管理数据");
+                List<ExamStudent> examStudentList = JSON.parseArray(JSON.toJSONString(result.get(SystemConstant.DATASOURCE)), ExamStudent.class);
+                examStudentService.saveOrUpdateBatch(examStudentList);
+                SystemConstant.addSummary(stringJoinerSummary, "写入考生管理数据完成,开始更新扫描任务数据");
+
+                RecognitionTypeEnum recognitionType = (RecognitionTypeEnum) map.get("recognitionType");
+                StoreTypeEnum storeType = (StoreTypeEnum) map.get("storeType");
+                // 更新扫描任务数据
+                paperScanTaskService.updatePaperScanTask(tbTask.getExamId(), recognitionType, storeType, examStudentList, tbTask.getCreateId());
+                SystemConstant.addSummary(stringJoinerSummary, "更新扫描任务数据完成,导入结束");
+                tbTask.setResult(TaskResultEnum.SUCCESS);
+
+                stringJoinerSummary.add(MessageFormat.format("{0}{1}{2}{3}{4}{5}{6}{7}",
+                        DateUtil.format(new Date(), SystemConstant.DEFAULT_DATE_PATTERN), FINISH_TITLE, dataCount,
+                        FINISH_TOTAL_SIZE, successCount, FINISH_SUCCESS_SIZE, errorCount, FINISH_ERROR_SIZE));
+            }
+        } catch (Exception e) {
+            tbTask.setResult(TaskResultEnum.ERROR);
+            stringJoinerSummary.add(MessageFormat.format("{0}{1}{2}{3}", DateUtil.format(new Date(), SystemConstant.DEFAULT_DATE_PATTERN), EXCEPTION_TITLE, EXCEPTION_DATA, e.getMessage()));
+        } finally {//生成文件
+            tbTask.setStatus(TaskStatusEnum.FINISH);
+            tbTask.setSummary(stringJoinerSummary.toString());
+            tbTaskService.updateById(tbTask);
+        }
+        return ResultUtil.ok();
+    }
+
+    private void createErrorExcelFile(String[] columnName, List<ExamStudentImport> examStudentDtoList, File excelFileTemp) {
+        try {
+            List<String> columnNameList = new ArrayList<>(Arrays.asList(columnName));
+            columnNameList.add("错误信息");
+            String[] columnNames = columnNameList.toArray(new String[0]);
+            List<String[]> columnValues = new ArrayList<>();
+            for (ExamStudentImport examStudentImport : examStudentDtoList) {
+                List<String> valueList = new ArrayList<>();
+                for (String title : columnNameList) {
+                    if (title.equals("错误信息")) {
+                        valueList.add(examStudentImport.getErrorMsg());
+                        continue;
+                    }
+                    String value = "";
+                    for (CodeNameEnableDisabledValue codeNameEnableValue : examStudentImport.getRequiredFieldList()) {
+                        if (codeNameEnableValue.getName().equals(title)) {
+                            value = codeNameEnableValue.getValue();
+                        }
+                    }
+                    if (StringUtils.isBlank(value)) {
+                        for (CodeNameEnableDisabledValue codeNameEnableValue : examStudentImport.getExtendFieldList()) {
+                            if (codeNameEnableValue.getName().equals(title)) {
+                                value = codeNameEnableValue.getValue();
+                            }
+                        }
+                    }
+                    valueList.add(value);
+                }
+                // 错误信息
+                String[] columnValue = valueList.toArray(new String[valueList.size()]);
+                columnValues.add(columnValue);
+            }
+
+            FileOutputStream outputStream = new FileOutputStream(excelFileTemp);
+            ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
+            writer.writeDataArrays("考生数据", null, columnNames, columnValues.listIterator());
+            writer.output(outputStream);
+            outputStream.flush();
+            outputStream.close();
+        } catch (Exception e) {
+
+        }
+    }
+
+}

+ 3 - 2
paper-library-business/src/main/java/com/qmth/paper/library/business/templete/execute/AsyncSysOrgImportService.java

@@ -62,9 +62,10 @@ public class AsyncSysOrgImportService extends AsyncImportTaskTemplate {
             } else {
                 ResultUtil.error(e.getMessage());
             }
-        } finally {//生成txt文件
+        } finally {
             tbTask.setSummary(stringJoinerSummary.toString());
-            super.createTxt(tbTask);
+            tbTask.setStatus(TaskStatusEnum.FINISH);
+            tbTaskService.updateById(tbTask);
         }
         return ResultUtil.ok(map);
     }

+ 3 - 2
paper-library-business/src/main/java/com/qmth/paper/library/business/templete/execute/AsyncSysUserDataImportService.java

@@ -60,9 +60,10 @@ public class AsyncSysUserDataImportService extends AsyncImportTaskTemplate {
             } else {
                 ResultUtil.error(e.getMessage());
             }
-        } finally {//生成txt文件
+        } finally {
             tbTask.setSummary(stringJoinerSummary.toString());
-            super.createTxt(tbTask);
+            tbTask.setStatus(TaskStatusEnum.FINISH);
+            tbTaskService.updateById(tbTask);
         }
         return ResultUtil.ok(map);
     }

+ 0 - 92
paper-library-business/src/main/java/com/qmth/paper/library/business/templete/export/AsyncExportTaskTemplate.java

@@ -1,33 +1,12 @@
 package com.qmth.paper.library.business.templete.export;
 
-import cn.hutool.core.date.DateUtil;
-import com.alibaba.fastjson.JSONObject;
-import com.qmth.boot.api.exception.ApiException;
 import com.qmth.paper.library.business.templete.importData.AsyncImportTaskTemplate;
-import com.qmth.paper.library.common.contant.SpringContextHolder;
-import com.qmth.paper.library.common.contant.SysSettingConstant;
-import com.qmth.paper.library.common.contant.SystemConstant;
-import com.qmth.paper.library.common.entity.TBTask;
-import com.qmth.paper.library.common.enums.TaskResultEnum;
-import com.qmth.paper.library.common.enums.TaskStatusEnum;
-import com.qmth.paper.library.common.enums.UploadFileEnum;
-import com.qmth.paper.library.common.service.CommonCacheService;
-import com.qmth.paper.library.common.service.TBTaskService;
-import com.qmth.paper.library.common.util.FileStoreUtil;
 import com.qmth.paper.library.common.util.Result;
-import com.qmth.paper.library.common.util.ResultUtil;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.scheduling.annotation.Async;
 
-import java.io.*;
-import java.text.MessageFormat;
-import java.util.Date;
 import java.util.Map;
-import java.util.Objects;
-import java.util.StringJoiner;
 
 /**
  * @Description: 异步导出模版
@@ -61,75 +40,4 @@ public abstract class AsyncExportTaskTemplate {
      *
      * @param tbTask
      */
-    public void createTxt(TBTask tbTask) {
-        FileStoreUtil fileStoreUtil = SpringContextHolder.getBean(FileStoreUtil.class);
-        TBTaskService tbTaskService = SpringContextHolder.getBean(TBTaskService.class);
-        ByteArrayOutputStream out = null;
-        InputStream inputStream = null;
-        try {
-            String charset = SystemConstant.CHARSET_GBK;
-            CommonCacheService commonCacheService = SpringContextHolder.getBean(CommonCacheService.class);
-            if (commonCacheService != null) {
-                String txtCharset = commonCacheService.getSysSettingGlobal().get(SysSettingConstant.SYS_TXT_CHARSET);
-                if (StringUtils.isNotBlank(txtCharset)) {
-                    charset = txtCharset;
-                }
-            }
-
-            out = new ByteArrayOutputStream();
-            out.write(tbTask.getSummary().getBytes(charset));
-            byte[] bookByteAry = out.toByteArray();
-            inputStream = new ByteArrayInputStream(bookByteAry);
-
-            JSONObject jsonObject = JSONObject.parseObject(tbTask.getExportFilePath());
-            if (Objects.isNull(jsonObject)) {
-                jsonObject = new JSONObject();
-                jsonObject.put(SystemConstant.TYPE, fileStoreUtil.getType());
-                String rootPath = fileStoreUtil.buildPath(UploadFileEnum.FILE, true);
-                String dirName = rootPath + File.separator + SystemConstant.getUuid() + TXT_PREFIX;
-                jsonObject.put(SystemConstant.PATH, dirName);
-            }
-            String path = (String) jsonObject.get(SystemConstant.PATH);
-            String type = (String) jsonObject.get(SystemConstant.TYPE);
-
-            path = fileStoreUtil.replaceSlash(path);
-            path = path.substring(0, path.lastIndexOf(SystemConstant.SINGLE_SLASH) + 1);
-
-            StringJoiner stringJoiner = new StringJoiner("");
-            stringJoiner.add(path).add(SystemConstant.getUuid()).add(TXT_PREFIX).toString();
-
-            fileStoreUtil.fileUpload(stringJoiner.toString(), inputStream, DigestUtils.md5Hex(new ByteArrayInputStream(bookByteAry)));
-
-            JSONObject json = new JSONObject();
-            json.put(SystemConstant.PATH, stringJoiner.toString());
-            json.put(SystemConstant.TYPE, type);
-            json.put(SystemConstant.UPLOAD_TYPE, UploadFileEnum.FILE);
-            tbTask.setTxtFilePath(json.toJSONString());
-        } catch (Exception e) {
-            log.error(SystemConstant.LOG_ERROR, e);
-            StringJoiner stringJoinerSummary = new StringJoiner("").add(tbTask.getSummary()).add("\n");
-            stringJoinerSummary.add(MessageFormat.format("{0}{1}{2}{3}", DateUtil.format(new Date(), SystemConstant.DEFAULT_DATE_PATTERN), EXCEPTION_CREATE_TXT_TITLE, EXCEPTION_DATA, e.getMessage()));
-            tbTask.setSummary(stringJoinerSummary.toString());
-            tbTask.setResult(TaskResultEnum.ERROR);
-            if (e instanceof ApiException) {
-                ResultUtil.error((ApiException) e, e.getMessage());
-            } else {
-                ResultUtil.error(e.getMessage());
-            }
-        } finally {
-            try {
-                if (Objects.nonNull(inputStream)) {
-                    inputStream.close();
-                }
-                if (Objects.nonNull(out)) {
-                    out.flush();
-                    out.close();
-                }
-            } catch (IOException e) {
-                log.error(SystemConstant.LOG_ERROR, e);
-            }
-            tbTask.setStatus(TaskStatusEnum.FINISH);
-            tbTaskService.updateById(tbTask);
-        }
-    }
 }

+ 9 - 118
paper-library-business/src/main/java/com/qmth/paper/library/business/templete/importData/AsyncImportTaskTemplate.java

@@ -1,33 +1,18 @@
 package com.qmth.paper.library.business.templete.importData;
 
-import cn.hutool.core.date.DateUtil;
-import com.alibaba.fastjson.JSONObject;
-import com.qmth.boot.api.exception.ApiException;
+import com.alibaba.fastjson.JSON;
+import com.qmth.paper.library.business.bean.vo.FilePathVo;
+import com.qmth.paper.library.business.service.FileUploadService;
 import com.qmth.paper.library.common.contant.SpringContextHolder;
-import com.qmth.paper.library.common.contant.SysSettingConstant;
-import com.qmth.paper.library.common.contant.SystemConstant;
 import com.qmth.paper.library.common.entity.TBTask;
-import com.qmth.paper.library.common.enums.TaskResultEnum;
-import com.qmth.paper.library.common.enums.TaskStatusEnum;
-import com.qmth.paper.library.common.enums.UploadFileEnum;
-import com.qmth.paper.library.common.service.CommonCacheService;
-import com.qmth.paper.library.common.service.TBTaskService;
-import com.qmth.paper.library.common.util.FileStoreUtil;
 import com.qmth.paper.library.common.util.Result;
-import com.qmth.paper.library.common.util.ResultUtil;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.scheduling.annotation.Async;
 
-import java.io.*;
-import java.nio.file.Files;
-import java.text.MessageFormat;
-import java.util.Date;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.Map;
-import java.util.Objects;
-import java.util.StringJoiner;
 
 /**
  * @Description: 异步导入模版
@@ -63,105 +48,11 @@ public abstract class AsyncImportTaskTemplate {
      *
      * @param tbTask
      * @return
-     * @throws IOException
      */
-    public InputStream getUploadFileInputStream(TBTask tbTask) throws Exception {
-        FileStoreUtil fileStoreUtil = SpringContextHolder.getBean(FileStoreUtil.class);
-        JSONObject jsonObject = JSONObject.parseObject(tbTask.getImportFilePath());
-        String path = (String) jsonObject.get(SystemConstant.PATH);
-        String type = (String) jsonObject.get(SystemConstant.TYPE);
-        UploadFileEnum uploadType = Enum.valueOf(UploadFileEnum.class, (String) jsonObject.get(SystemConstant.UPLOAD_TYPE));
-        InputStream inputStream = null;
-        if (Objects.equals(type, SystemConstant.OSS)) {
-            // todo 20221228 解决oss问题
-//            inputStream = fileStoreUtil.fileDownload(path, uploadType.getFssType());
-        } else {
-            inputStream = Files.newInputStream(new File(path).toPath());
-        }
-        return inputStream;
+    public InputStream getUploadFileInputStream(TBTask tbTask) {
+        FileUploadService fileUploadService = SpringContextHolder.getBean(FileUploadService.class);
+        FilePathVo filePathVo = JSON.parseObject(tbTask.getImportFilePath(), FilePathVo.class);
+        return fileUploadService.downloadInputStream(filePathVo.getPath(), filePathVo.getType());
     }
 
-    /**
-     * 创建txt文件
-     *
-     * @param tbTask
-     */
-    public void createTxt(TBTask tbTask) {
-        FileStoreUtil fileStoreUtil = SpringContextHolder.getBean(FileStoreUtil.class);
-        TBTaskService tbTaskService = SpringContextHolder.getBean(TBTaskService.class);
-        ByteArrayOutputStream out = null;
-        InputStream inputStream = null;
-        try {
-            String charset = SystemConstant.CHARSET_GBK;
-            CommonCacheService commonCacheService = SpringContextHolder.getBean(CommonCacheService.class);
-            if (commonCacheService != null) {
-                String txtCharset = commonCacheService.getSysSettingGlobal().get(SysSettingConstant.SYS_TXT_CHARSET);
-                if (StringUtils.isNotBlank(txtCharset)) {
-                    charset = txtCharset;
-                }
-            }
-
-            out = new ByteArrayOutputStream();
-            out.write(tbTask.getSummary().getBytes(charset));
-            byte[] bookByteAry = out.toByteArray();
-            inputStream = new ByteArrayInputStream(bookByteAry);
-
-            JSONObject jsonObject = JSONObject.parseObject(tbTask.getImportFilePath());
-            if (Objects.isNull(jsonObject)) {
-                jsonObject = new JSONObject();
-                String rootPath = fileStoreUtil.buildPath(UploadFileEnum.FILE, true);
-                String dirName = rootPath + File.separator + SystemConstant.getUuid() + TXT_PREFIX;
-                jsonObject.put(SystemConstant.TYPE, fileStoreUtil.getType());
-                jsonObject.put(SystemConstant.PATH, dirName);
-            }
-            String path = (String) jsonObject.get(SystemConstant.PATH);
-            String type = (String) jsonObject.get(SystemConstant.TYPE);
-
-            path = fileStoreUtil.replaceSlash(path);
-            path = path.substring(0, path.lastIndexOf(SystemConstant.SINGLE_SLASH) + 1);
-
-            StringJoiner stringJoiner = new StringJoiner("");
-            stringJoiner.add(path).add(SystemConstant.getUuid()).add(TXT_PREFIX).toString();
-
-            fileStoreUtil.fileUpload(stringJoiner.toString(), inputStream, DigestUtils.md5Hex(new ByteArrayInputStream(bookByteAry)));
-
-            JSONObject json = new JSONObject();
-            json.put(SystemConstant.PATH, stringJoiner.toString());
-            json.put(SystemConstant.TYPE, type);
-            json.put(SystemConstant.UPLOAD_TYPE, UploadFileEnum.FILE);
-
-            tbTask.setTxtFilePath(json.toJSONString());
-        } catch (Exception e) {
-            log.error(SystemConstant.LOG_ERROR, e);
-            StringJoiner stringJoinerSummary = new StringJoiner("").add(tbTask.getSummary()).add("\n");
-            stringJoinerSummary.add(MessageFormat.format("{0}{1}{2}{3}", DateUtil.format(new Date(), SystemConstant.DEFAULT_DATE_PATTERN), EXCEPTION_CREATE_TXT_TITLE, EXCEPTION_DATA, e.getMessage()));
-
-            String summary = stringJoinerSummary.toString();
-            if (summary.length() >= 65535) {
-                summary = "Data too long : " + summary.substring(0, 100) + "......" + summary.substring(summary.length() - 100);
-            }
-
-            tbTask.setSummary(summary);
-            tbTask.setResult(TaskResultEnum.ERROR);
-            if (e instanceof ApiException) {
-                ResultUtil.error((ApiException) e, e.getMessage());
-            } else {
-                ResultUtil.error(e.getMessage());
-            }
-        } finally {
-            try {
-                if (Objects.nonNull(inputStream)) {
-                    inputStream.close();
-                }
-                if (Objects.nonNull(out)) {
-                    out.flush();
-                    out.close();
-                }
-            } catch (IOException e) {
-                log.error(SystemConstant.LOG_ERROR, e);
-            }
-            tbTask.setStatus(TaskStatusEnum.FINISH);
-            tbTaskService.updateById(tbTask);
-        }
-    }
 }

+ 2 - 0
paper-library-business/src/main/java/com/qmth/paper/library/business/templete/service/TaskLogicService.java

@@ -1,6 +1,7 @@
 package com.qmth.paper.library.business.templete.service;
 
 import java.util.Map;
+import java.util.StringJoiner;
 
 /**
  * @Description: 任务处理逻辑
@@ -43,4 +44,5 @@ public interface TaskLogicService {
      * @param map 数据源
      */
     Map<String, Object> executeDownloadPictureLogic(Map<String, Object> map);
+    Map<String, Object> executeImportExamStudentLogic(Map<String, Object> map, StringJoiner stringJoinerSummary);
 }

+ 258 - 6
paper-library-business/src/main/java/com/qmth/paper/library/business/templete/service/impl/TaskLogicServiceImpl.java

@@ -6,6 +6,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.google.common.collect.Lists;
 import com.qmth.boot.api.exception.ApiException;
+import com.qmth.boot.tools.excel.ExcelReader;
+import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.boot.tools.excel.model.DataMap;
+import com.qmth.paper.library.business.bean.dto.CodeNameEnableDisabledValue;
+import com.qmth.paper.library.business.bean.dto.ExamStudentImport;
+import com.qmth.paper.library.business.bean.dto.ExamStudentParseDto;
 import com.qmth.paper.library.business.bean.result.DocManageDetailResult;
 import com.qmth.paper.library.business.bean.result.PictureManageDetailResult;
 import com.qmth.paper.library.business.entity.PaperLibrary;
@@ -23,12 +29,10 @@ import com.qmth.paper.library.common.entity.*;
 import com.qmth.paper.library.common.enums.ExceptionResultEnum;
 import com.qmth.paper.library.common.enums.StoreTypeEnum;
 import com.qmth.paper.library.common.enums.UploadFileEnum;
-import com.qmth.paper.library.common.service.BasicSchoolService;
-import com.qmth.paper.library.common.service.BasicSemesterService;
-import com.qmth.paper.library.common.service.SysOrgService;
-import com.qmth.paper.library.common.service.SysUserService;
+import com.qmth.paper.library.common.service.*;
 import com.qmth.paper.library.common.util.*;
 import com.qmth.paper.library.common.util.excel.ExcelError;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.xssf.usermodel.*;
@@ -37,13 +41,16 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.util.CollectionUtils;
 import org.springframework.util.LinkedMultiValueMap;
 
 import javax.annotation.Resource;
 import java.io.*;
+import java.lang.reflect.Field;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @Description: 任务处理逻辑impl
@@ -82,6 +89,7 @@ public class TaskLogicServiceImpl implements TaskLogicService {
     private BasicExamService basicExamService;
     @Resource
     private PictureManageService pictureManageService;
+    private BasicFieldService basicFieldService;
 
     @Transactional
     @Override
@@ -284,7 +292,6 @@ public class TaskLogicServiceImpl implements TaskLogicService {
         return map;
     }
 
-    @Override
     public Map<String, Object> executeDownloadPictureLogic(Map<String, Object> map) {
         List<File> sourceFileList = new ArrayList<>();
         File zipFile = null;
@@ -538,6 +545,168 @@ public class TaskLogicServiceImpl implements TaskLogicService {
     }
 
 
+    public Map<String, Object> executeImportExamStudentLogic(Map<String, Object> map, StringJoiner stringJoinerSummary) {
+        SysUser sysUser = (SysUser) map.get(SystemConstant.USER);
+        InputStream inputStream = (InputStream) map.get("inputStream");
+        Long examId = SystemConstant.convertIdToLong(String.valueOf(map.get("examId")));
+        StoreTypeEnum storeType = StoreTypeEnum.valueOf(String.valueOf(map.get("storeType")));
+        BasicExam basicExam = basicExamService.getById(examId);
+        if (Objects.isNull(basicExam)) {
+            throw ExceptionResultEnum.ERROR.exception("考试不存在");
+        }
+        Long semesterId = basicExam.getSemesterId();
+
+        SystemConstant.addSummary(stringJoinerSummary, "开始解析导入文件内容");
+        ExamStudentParseDto examStudentParseDto;
+        try {
+            // 解析excel文件内容
+            examStudentParseDto = this.parseImportExamStudentData(sysUser.getSchoolId(), inputStream);
+        } catch (Exception e) {
+            SystemConstant.addSummary(stringJoinerSummary, "解析导入文件内容结束,解析失败。" + e.getMessage());
+            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
+        }
+        SystemConstant.addSummary(stringJoinerSummary, "解析导入文件内容结束,开始进行数据校验");
+
+        List<ExamStudentImport> examStudentImportList = examStudentParseDto.getBasicExamStudentImportList();
+        if (CollectionUtils.isEmpty(examStudentImportList)) {
+            throw ExceptionResultEnum.ERROR.exception("未读取到有效数据");
+        }
+
+        // 考生管理中,当前考试下已有考生集合
+        List<ExamStudent> examStudentList = examStudentService.list(new QueryWrapper<ExamStudent>().lambda().eq(ExamStudent::getExamId, examId));
+        Map<String, ExamStudent> courseIdStudentCodeMap = examStudentList.stream().collect(Collectors.toMap(k -> k.getCourseCode() + SystemConstant.HYPHEN + k.getStudentCode(), Function.identity()));
+
+        // 扫描任务管理中,当前考试下已有任务集合
+        List<PaperScanTask> paperScanTaskList = paperScanTaskService.listByExamId(examId);
+        Map<String, StoreTypeEnum> stringStoreTypeMap = paperScanTaskList.stream().collect(Collectors.toMap(PaperScanTask::getCourseCode, PaperScanTask::getStoreType));
+
+        Map<String, String> courseMap = examStudentList.stream().collect(Collectors.toMap(ExamStudent::getCourseCode, ExamStudent::getCourseName));
+        Map<String, List<String>> studentCodeMap = new HashMap<>();
+        // 是否有错误提示
+        boolean hasError = false;
+        List<ExamStudent> examStudents = new ArrayList<>();
+        for (ExamStudentImport examStudentImport : examStudentImportList) {
+            ExamStudent examStudent = new ExamStudent();
+            BeanUtils.copyProperties(examStudentImport, examStudent);
+            examStudent.setExtendFields(CollectionUtils.isNotEmpty(examStudentImport.getExtendFieldList()) ? JSON.toJSONString(examStudentImport.getExtendFieldList()) : null);
+
+            StringJoiner stringJoiner = new StringJoiner(";");
+            // 学号去掉空格
+            examStudent.setStudentCode(StringUtils.isNotBlank(examStudentImport.getStudentCode()) ? StringUtils.deleteWhitespace(examStudentImport.getStudentCode()) : null);
+            // 课程代码去掉空格
+            examStudent.setCourseCode(StringUtils.isNotBlank(examStudentImport.getCourseCode()) ? StringUtils.deleteWhitespace(examStudentImport.getCourseCode()) : null);
+            // 课程名称去掉空格
+            examStudent.setCourseName(StringUtils.isNotBlank(examStudentImport.getCourseName()) ? StringUtils.deleteWhitespace(examStudentImport.getCourseName()) : null);
+            // 考场去掉空格
+            examStudent.setExamRoom(StringUtils.isNotBlank(examStudentImport.getExamRoom()) ? StringUtils.deleteWhitespace(examStudentImport.getExamRoom()) : null);
+            // 班级去掉空格
+            examStudent.setClassName(StringUtils.isNotBlank(examStudentImport.getClassName()) ? StringUtils.deleteWhitespace(examStudentImport.getClassName()) : null);
+
+
+            StringJoiner errorMsg = new StringJoiner(";");
+            if (StringUtils.isBlank(examStudent.getStudentCode())) {
+                errorMsg.add("学号必填");
+            }
+            if (StringUtils.isBlank(examStudent.getStudentName())) {
+                errorMsg.add("姓名必填");
+            }
+            if (StringUtils.isBlank(examStudent.getCourseCode())) {
+                errorMsg.add("课程代码必填");
+            }
+            if (StringUtils.isBlank(examStudent.getCourseName())) {
+                errorMsg.add("课程名称必填");
+            }
+            if (StoreTypeEnum.ROOM.equals(storeType) && StringUtils.isBlank(examStudent.getExamRoom())) {
+                errorMsg.add("考场必填");
+            }
+            if (StoreTypeEnum.CLASS.equals(storeType) && StringUtils.isBlank(examStudent.getClassName())) {
+                errorMsg.add("班级必填");
+            }
+
+            if (StringUtils.isNoneBlank(examStudent.getCourseCode(), examStudent.getCourseName())) {
+                if (courseMap.containsKey(examStudent.getCourseCode())) {
+                    if (!examStudent.getCourseName().equals(courseMap.get(examStudent.getCourseCode()))) {
+                        errorMsg.add("课程代码[" + examStudent.getCourseCode() + "]存在多个课程名称[" + courseMap.get(examStudent.getCourseCode()) + "," + examStudent.getCourseName() + "]");
+                    }
+                } else {
+                    courseMap.put(examStudent.getCourseCode(), examStudent.getCourseName());
+                }
+            }
+            // 考生在同课程下重复
+            if (StringUtils.isNoneBlank(examStudent.getCourseCode(), examStudent.getStudentCode())) {
+                if (studentCodeMap.containsKey(examStudent.getCourseCode())) {
+                    List<String> studentCodeList = studentCodeMap.get(examStudent.getCourseCode());
+                    if (CollectionUtils.isEmpty(studentCodeList)) {
+                        studentCodeMap.put(examStudent.getCourseCode(), new ArrayList<>());
+                    } else {
+                        if (studentCodeList.contains(examStudent.getStudentCode())) {
+                            errorMsg.add("学号[" + examStudent.getStudentCode() + "]在课程代码[" + examStudent.getCourseCode() + "]下已存在");
+                        } else {
+                            studentCodeList.add(examStudent.getStudentCode());
+                            studentCodeMap.put(examStudent.getCourseCode(), studentCodeList);
+                        }
+                    }
+                } else {
+                    List<String> studentCodeList = new ArrayList<>();
+                    studentCodeList.add(examStudent.getStudentCode());
+                    studentCodeMap.put(examStudent.getCourseCode(), studentCodeList);
+                }
+
+                String key = examStudent.getCourseCode() + SystemConstant.HYPHEN + examStudent.getStudentCode();
+                if (courseIdStudentCodeMap.containsKey(key)) {
+                    ExamStudent student = courseIdStudentCodeMap.get(key);
+
+                    // 校验存储方式
+                    if (stringStoreTypeMap.containsKey(examStudent.getCourseCode())) {
+                        StoreTypeEnum courseCodeStoreType = stringStoreTypeMap.get(examStudent.getCourseCode());
+                        if (StoreTypeEnum.ROOM.equals(courseCodeStoreType) && !examStudent.getExamRoom().equals(student.getExamRoom())) {
+                            errorMsg.add("考场不能更改,原考场为[" + student.getExamRoom() + "]");
+                        }
+                        if (StoreTypeEnum.CLASS.equals(courseCodeStoreType) && !examStudent.getClassName().equals(student.getClassName())) {
+                            errorMsg.add("班级不能更改,原班级为[" + student.getClassName() + "]");
+                        }
+                    }
+                    examStudent.setId(student.getId());
+                }
+            }
+
+            if (StringUtils.isNotBlank(errorMsg.toString())) {
+                throw ExceptionResultEnum.ERROR.exception(errorMsg.toString());
+            }
+
+            if (examStudent.getId() == null) {
+                examStudent.setId(SystemConstant.getDbUuid());
+            }
+            examStudent.setSchoolId(sysUser.getSchoolId());
+            examStudent.setSemesterId(semesterId);
+            examStudent.setExamId(examId);
+            examStudent.setCreateId(sysUser.getId());
+            examStudent.setCreateTime(System.currentTimeMillis());
+
+            if (stringJoiner.toString().length() > 0) {
+                examStudentImport.setErrorMsg(stringJoiner.toString());
+            }
+
+            if (!hasError && stringJoiner.toString().length() > 0) {
+                hasError = true;
+            }
+
+            if (stringJoiner.toString().length() == 0) {
+                examStudents.add(examStudent);
+            }
+        }
+
+        map.put(SystemConstant.ERROR_DATA_LIST, examStudentImportList);
+        map.put(SystemConstant.DATASOURCE, examStudents);
+        map.put(SystemConstant.DATA_COUNT, examStudentImportList.size());
+        map.put(SystemConstant.SUCCESS_DATA_COUNT, examStudents.size());
+        map.put(SystemConstant.ERROR_DATA_COUNT, examStudentImportList.size() - examStudents.size());
+        map.put(SystemConstant.HAS_ERROR_DATA, hasError);
+        map.put(SystemConstant.COLUMN_NAMES, examStudentParseDto.getColumnNames());
+
+        return map;
+    }
+
     /**
      * 生成清单
      *
@@ -677,4 +846,87 @@ public class TaskLogicServiceImpl implements TaskLogicService {
         wb.write(fout);
         fout.close();
     }
+
+    private ExamStudentParseDto parseImportExamStudentData(Long schoolId, InputStream inputStream) {
+        ExcelReader excelReader = ExcelReader.create(ExcelType.XLSX, inputStream, 1);
+        List<DataMap> dataMapList;
+        try {
+            dataMapList = excelReader.getDataMapList();
+        } catch (Exception e) {
+            throw ExceptionResultEnum.ERROR.exception("读取excel内容失败");
+        }
+        ExamStudentParseDto examStudentParseDto = new ExamStudentParseDto();
+        List<ExamStudentImport> list = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(dataMapList)) {
+            String[] columnNames = excelReader.getColumnNames();
+
+            List<BasicField> basicFieldList = basicFieldService.listBySchoolId(schoolId);
+            // 必填字段
+            Map<String, BasicField> requiredMap = basicFieldList.stream().filter(m -> m.getBasicField()).collect(Collectors.toMap(BasicField::getName, e -> e));
+            // 扩展字段
+            Map<String, BasicField> extendMap = basicFieldList.stream().filter(m -> !m.getBasicField()).collect(Collectors.toMap(BasicField::getName, e -> e));
+
+            // 通用规则表头
+            List<String> actualTitleList = Stream.concat(requiredMap.keySet().stream(), extendMap.keySet().stream()).collect(Collectors.toList());
+            if (!org.apache.commons.collections4.CollectionUtils.isEqualCollection(Arrays.asList(columnNames), actualTitleList)) {
+                throw ExceptionResultEnum.ERROR.exception("表头名称错误,正确表头为【" + String.join(",", actualTitleList) + "】");
+            }
+
+            examStudentParseDto.setColumnNames(columnNames);
+
+            dataMapList.forEach(m -> {
+                if (m.size() > 0) {
+                    ExamStudentImport examStudentImport = new ExamStudentImport();
+                    Class<ExamStudentImport> aClass = (Class<ExamStudentImport>) examStudentImport.getClass();
+                    for (Map.Entry<String, String> entry : m.entrySet()) {
+                        // 必填字段
+                        if (requiredMap.containsKey(entry.getKey())) {
+                            BasicField basicField = requiredMap.get(entry.getKey());
+                            try {
+                                Field declaredField = aClass.getDeclaredField(basicField.getCode());
+                                declaredField.setAccessible(true);
+                                declaredField.set(examStudentImport, StringUtils.isNotBlank(entry.getValue()) ? StringUtils.deleteWhitespace(entry.getValue()) : null);
+                            } catch (NoSuchFieldException e) {
+                                throw ExceptionResultEnum.ERROR.exception("未获取到表头为[" + entry.getKey() + "]的属性值");
+                            } catch (IllegalAccessException e) {
+                                throw ExceptionResultEnum.ERROR.exception("表头为[" + entry.getKey() + "]的值赋值失败");
+                            }
+                            examStudentImport.getRequiredFieldList().add(new CodeNameEnableDisabledValue(basicField.getCode(), basicField.getName(), basicField.getEnable(), basicField.getDisabled(), StringUtils.isNotBlank(entry.getValue()) ? entry.getValue() : null));
+                        }
+                        // 扩展字段
+                        if (extendMap.containsKey(entry.getKey())) {
+                            BasicField basicField = extendMap.get(entry.getKey());
+                            examStudentImport.getExtendFieldList().add(new CodeNameEnableDisabledValue(basicField.getCode(), basicField.getName(), basicField.getEnable(), basicField.getDisabled(), StringUtils.isNotBlank(entry.getValue()) ? entry.getValue() : null));
+                        }
+                    }
+
+                    List<CodeNameEnableDisabledValue> requiredFieldList = examStudentImport.getRequiredFieldList();
+                    // 必填字段
+                    if (requiredMap.keySet().size() > requiredFieldList.size()) {
+                        for (String s : requiredMap.keySet()) {
+                            Optional<CodeNameEnableDisabledValue> optional = examStudentImport.getRequiredFieldList().stream().filter(r -> s.equals(r.getName())).findFirst();
+                            if (!optional.isPresent()) {
+                                BasicField basicField = requiredMap.get(s);
+                                examStudentImport.getRequiredFieldList().add(new CodeNameEnableDisabledValue(basicField.getCode(), basicField.getName(), basicField.getEnable(), basicField.getDisabled(), null));
+                            }
+                        }
+                    }
+                    List<CodeNameEnableDisabledValue> extendFieldList = examStudentImport.getExtendFieldList();
+                    // 扩展字段
+                    if (extendMap.keySet().size() > extendFieldList.size()) {
+                        for (String s : extendMap.keySet()) {
+                            Optional<CodeNameEnableDisabledValue> optional = examStudentImport.getExtendFieldList().stream().filter(r -> s.equals(r.getName())).findFirst();
+                            if (!optional.isPresent()) {
+                                BasicField basicField = extendMap.get(s);
+                                examStudentImport.getExtendFieldList().add(new CodeNameEnableDisabledValue(basicField.getCode(), basicField.getName(), basicField.getEnable(), basicField.getDisabled(), null));
+                            }
+                        }
+                    }
+                    list.add(examStudentImport);
+                }
+            });
+        }
+        examStudentParseDto.setBasicExamStudentImportList(list);
+        return examStudentParseDto;
+    }
 }

+ 33 - 0
paper-library-common/src/main/java/com/qmth/paper/library/common/contant/SystemConstant.java

@@ -1,5 +1,6 @@
 package com.qmth.paper.library.common.contant;
 
+import cn.hutool.core.date.DateUtil;
 import com.aventrix.jnanoid.jnanoid.NanoIdUtils;
 import com.qmth.boot.core.uid.service.UidService;
 import com.qmth.paper.library.common.annotation.DBVerify;
@@ -17,6 +18,7 @@ import java.io.*;
 import java.lang.reflect.Field;
 import java.net.URLEncoder;
 import java.nio.charset.Charset;
+import java.text.MessageFormat;
 import java.util.*;
 
 /**
@@ -39,6 +41,7 @@ public class SystemConstant {
     public static final String SUCCESS = "success";
     public static final String EXTEND_COLUMN = "extendColumn";
     public static final String IMAGE_TEMP = "image-temp";
+    public static final String TEMP = "temp";
     public static final String SESSION = "session:";
     public static final String TASK = "task";
     public static final String TB_TASK_ID = "tbTaskId";
@@ -63,6 +66,7 @@ public class SystemConstant {
     public static final String ID = "id";
     public static final String FILE = "file";
     public static final String SIZE = "size";
+    public static final String EXCEL_PREFIX = ".xlsx";
     public static final String ZIP_SUFFIX = ".zip";
     public static final String SCAN_TASK_CODE_PREFIX = "T";
     public static final String TMP_DIR = "java.io.tmpdir";
@@ -74,6 +78,7 @@ public class SystemConstant {
     public static final int PAGE_NUMBER_MIN = 1;
     public static final int IN_SIZE_MAX = 1000;
     public static final String COMMA = ",";
+    public static final String HYPHEN = "-";
     public static final String ADMIN_CODE = "admin";
     public static final String AUTH = "auth";//命题老师id
     //    public static final int MAX_RETRY_CREATE_PDF_COUNT = 5;
@@ -91,6 +96,14 @@ public class SystemConstant {
     public static final String REGULAR_EXPRESSION_OF_NUMBER = "^\\d{n}$";
     public static final String REGULAR_EXPRESSION_OF_NUMBER_LETTER = "^\\w+$";
 
+    public static final String ERROR_DATA_LIST = "errorDataList";
+    public static final String DATASOURCE = "datasource";
+    public static final String DATA_COUNT = "dataCount";
+    public static final String SUCCESS_DATA_COUNT = "successDataCount";
+    public static final String ERROR_DATA_COUNT = "errorDataCount";
+    public static final String HAS_ERROR_DATA = "hasErrorData";
+    public static final String COLUMN_NAMES = "columnNames";
+
 
     public static final String DOUBLE_SLASH = "\\\\";
     public static final String SINGLE_SLASH = "/";
@@ -395,4 +408,24 @@ public class SystemConstant {
         File file = new File(System.getProperty(SystemConstant.TMP_DIR), SystemConstant.getNanoId() + suffix);
         return file;
     }
+
+    /**
+     * 获取临时文件
+     *
+     * @param suffix
+     * @return
+     */
+    public static File getFileTempVar(String suffix) throws IOException {
+        File fileTmpDir = new File(System.getProperty(SystemConstant.TMP_DIR));
+        if (!fileTmpDir.exists()) {
+            fileTmpDir.mkdirs();
+        }
+        File file = File.createTempFile(SystemConstant.TEMP, suffix);
+        return file;
+    }
+
+    public static void addSummary(StringJoiner stringJoinerSummary, String message) {
+        // 时间:事件
+        stringJoinerSummary.add(MessageFormat.format("{0}{1}{2}", DateUtil.format(new Date(), SystemConstant.DEFAULT_DATE_PATTERN), "->", message));
+    }
 }

+ 165 - 0
paper-library-common/src/main/java/com/qmth/paper/library/common/entity/BasicField.java

@@ -0,0 +1,165 @@
+package com.qmth.paper.library.common.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.io.Serializable;
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * <p>
+ * 字段管理表
+ * </p>
+ *
+ * @author xf
+ * @since 2024-07-30
+ */
+@TableName("basic_field")
+@ApiModel(value="BasicField对象", description="字段管理表")
+public class BasicField implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.INPUT)
+    private Long id;
+
+    @ApiModelProperty(value = "学校ID")
+    private Long schoolId;
+
+    @ApiModelProperty(value = "字段代码")
+    private String code;
+
+    @ApiModelProperty(value = "字段名称")
+    private String name;
+
+    @ApiModelProperty(value = "是否选中")
+    private Boolean enable;
+
+    @ApiModelProperty(value = "是否禁用")
+    private Boolean disabled;
+
+    @ApiModelProperty(value = "是否基础字段")
+    private Boolean basicField;
+
+    private Long createId;
+
+    private Long createTime;
+
+    private Long updateId;
+
+    private Long updateTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+    public Long getSchoolId() {
+        return schoolId;
+    }
+
+    public void setSchoolId(Long schoolId) {
+        this.schoolId = schoolId;
+    }
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+    public Boolean getDisabled() {
+        return disabled;
+    }
+
+    public void setDisabled(Boolean disabled) {
+        this.disabled = disabled;
+    }
+
+    public Boolean getBasicField() {
+        return basicField;
+    }
+
+    public void setBasicField(Boolean basicField) {
+        this.basicField = basicField;
+    }
+    public Long getCreateId() {
+        return createId;
+    }
+
+    public void setCreateId(Long createId) {
+        this.createId = createId;
+    }
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+    public Long getUpdateId() {
+        return updateId;
+    }
+
+    public void setUpdateId(Long updateId) {
+        this.updateId = updateId;
+    }
+    public Long getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Long updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BasicField that = (BasicField) o;
+        return code.equals(that.code);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(code);
+    }
+
+    @Override
+    public String toString() {
+        return "BasicField{" +
+            "id=" + id +
+            ", schoolId=" + schoolId +
+            ", code=" + code +
+            ", name=" + name +
+            ", enable=" + enable +
+            ", disabled=" + disabled +
+            ", basicField=" + basicField +
+            ", createId=" + createId +
+            ", createTime=" + createTime +
+            ", updateId=" + updateId +
+            ", updateTime=" + updateTime +
+        "}";
+    }
+}

+ 10 - 0
paper-library-common/src/main/java/com/qmth/paper/library/common/entity/ExamStudent.java

@@ -89,6 +89,8 @@ public class ExamStudent extends BaseEntity implements Serializable {
     @ExcelColumn(name = "备注", index = 11, nullable = true)
     private String remark;
 
+    @ApiModelProperty(value = "扩展字段")
+    private String extendFields;
     @ApiModelProperty(value = "是否绑定时自动创建:0-导入或者新建,1-绑定时自动创建")
     private Boolean source;
 
@@ -239,6 +241,14 @@ public class ExamStudent extends BaseEntity implements Serializable {
         this.remark = remark;
     }
 
+    public String getExtendFields() {
+        return extendFields;
+    }
+
+    public void setExtendFields(String extendFields) {
+        this.extendFields = extendFields;
+    }
+
     public Boolean getSource() {
         return source;
     }

+ 14 - 14
paper-library-common/src/main/java/com/qmth/paper/library/common/entity/TBTask.java

@@ -33,9 +33,9 @@ public class TBTask extends BaseEntity implements Serializable {
     private Long schoolId;
 
     @JsonSerialize(using = ToStringSerializer.class)
-    @ApiModelProperty(value = "机构id")
-    @TableField(value = "org_id")
-    private Long orgId;
+    @ApiModelProperty(value = "考试id")
+    @TableField(value = "exam_id")
+    private Long examId;
 
     @ApiModelProperty(value = "任务类型")
     @TableField(value = "type")
@@ -65,9 +65,9 @@ public class TBTask extends BaseEntity implements Serializable {
     @TableField(value = "export_file_path")
     private String exportFilePath;
 
-    @ApiModelProperty(value = "报告路径")
-    @TableField(value = "txt_file_path")
-    private String txtFilePath;
+    @ApiModelProperty(value = "错误文件路径")
+    @TableField(value = "error_file_path")
+    private String errorFilePath;
 
     @ApiModelProperty(value = "备注")
     @TableField(value = "remark")
@@ -108,12 +108,12 @@ public class TBTask extends BaseEntity implements Serializable {
         this.schoolId = schoolId;
     }
 
-    public Long getOrgId() {
-        return orgId;
+    public Long getExamId() {
+        return examId;
     }
 
-    public void setOrgId(Long orgId) {
-        this.orgId = orgId;
+    public void setExamId(Long examId) {
+        this.examId = examId;
     }
 
     public TaskTypeEnum getType() {
@@ -172,12 +172,12 @@ public class TBTask extends BaseEntity implements Serializable {
         this.exportFilePath = exportFilePath;
     }
 
-    public String getTxtFilePath() {
-        return txtFilePath;
+    public String getErrorFilePath() {
+        return errorFilePath;
     }
 
-    public void setTxtFilePath(String txtFilePath) {
-        this.txtFilePath = txtFilePath;
+    public void setErrorFilePath(String errorFilePath) {
+        this.errorFilePath = errorFilePath;
     }
 
     public String getRemark() {

+ 71 - 0
paper-library-common/src/main/java/com/qmth/paper/library/common/enums/BasicFieldEnum.java

@@ -0,0 +1,71 @@
+package com.qmth.paper.library.common.enums;
+
+import com.qmth.paper.library.common.contant.SystemConstant;
+import com.qmth.paper.library.common.entity.BasicField;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 基础字段枚举
+ */
+public enum BasicFieldEnum {
+
+    STUDENT_CODE("studentCode", "学号", true, true),
+    STUDENT_NAME("studentName", "姓名", true, true),
+    COURSE_CODE("courseCode", "课程代码", true, true),
+    COURSE_NAME("courseName", "课程名称", true, true),
+    COLLEGE_NAME("collegeName", "学院", true, true),
+    MAJOR_NAME("majorName", "专业", true, false),
+    CLASS_NAME("className", "班级", true, false),
+    TEACHER("teacher", "任课老师", true, false),
+    TEACH_CLASS("teachClass", "教学班", true, true),
+    EXAM_ROOM("examRoom", "考场", true, true),
+    SCORE("score", "成绩", true, false),
+    REMARK("remark", "备注", true, false);
+
+    private final String code;
+    private final String name;
+
+    private final Boolean enable;
+    private final Boolean disabled;
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public Boolean getDisabled() {
+        return disabled;
+    }
+
+    BasicFieldEnum(String code, String name, Boolean enable, Boolean disabled) {
+        this.code = code;
+        this.name = name;
+        this.enable = enable;
+        this.disabled = disabled;
+    }
+
+    public static List<BasicField> list(Long schoolId){
+        List<BasicField> basicFieldList = new ArrayList<>();
+        for (BasicFieldEnum value : BasicFieldEnum.values()) {
+            BasicField basicField = new BasicField();
+            basicField.setId(SystemConstant.getDbUuid());
+            basicField.setSchoolId(schoolId);
+            basicField.setCode(value.getCode());
+            basicField.setName(value.getName());
+            basicField.setEnable(value.getEnable());
+            basicField.setDisabled(value.getDisabled());
+            basicField.setBasicField(true);
+            basicFieldList.add(basicField);
+        }
+        return basicFieldList;
+    }
+}

+ 4 - 3
paper-library-common/src/main/java/com/qmth/paper/library/common/enums/TaskTypeEnum.java

@@ -10,12 +10,13 @@ import java.util.Objects;
  * @Date: 2021/3/29
  */
 public enum TaskTypeEnum {
-    ORG_IMPORT("导入组织架构"),
-    USER_IMPORT("导入用户"),
+
+    ORG_IMPORT("组织架构导入"),
+    USER_IMPORT("用户导入"),
+    EXAM_STUDENT_IMPORT("考生导入"),
     PAPER_DOWNLOAD("批量下载"),
     PICTURE_DOWNLOAD("图片下载");
 
-
     private String title;
 
     private TaskTypeEnum(String title) {

+ 16 - 0
paper-library-common/src/main/java/com/qmth/paper/library/common/mapper/BasicFieldMapper.java

@@ -0,0 +1,16 @@
+package com.qmth.paper.library.common.mapper;
+
+import com.qmth.paper.library.common.entity.BasicField;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 字段管理表 Mapper 接口
+ * </p>
+ *
+ * @author xf
+ * @since 2024-07-30
+ */
+public interface BasicFieldMapper extends BaseMapper<BasicField> {
+
+}

+ 24 - 0
paper-library-common/src/main/java/com/qmth/paper/library/common/service/BasicFieldService.java

@@ -0,0 +1,24 @@
+package com.qmth.paper.library.common.service;
+
+import com.qmth.paper.library.common.entity.BasicField;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 字段管理表 服务类
+ * </p>
+ *
+ * @author xf
+ * @since 2024-07-30
+ */
+public interface BasicFieldService extends IService<BasicField> {
+
+    List<BasicField> listBySchoolId(Long schoolId);
+    List<BasicField> listData();
+
+    boolean savaData(List<BasicField> list);
+
+    boolean removeBySchoolId(Long schoolId);
+}

+ 95 - 0
paper-library-common/src/main/java/com/qmth/paper/library/common/service/impl/BasicFieldServiceImpl.java

@@ -0,0 +1,95 @@
+package com.qmth.paper.library.common.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.paper.library.common.contant.SystemConstant;
+import com.qmth.paper.library.common.entity.BasicField;
+import com.qmth.paper.library.common.entity.SysUser;
+import com.qmth.paper.library.common.enums.BasicFieldEnum;
+import com.qmth.paper.library.common.enums.ExceptionResultEnum;
+import com.qmth.paper.library.common.mapper.BasicFieldMapper;
+import com.qmth.paper.library.common.service.BasicFieldService;
+import com.qmth.paper.library.common.util.ServletUtil;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 字段管理表 服务实现类
+ * </p>
+ *
+ * @author xf
+ * @since 2024-07-30
+ */
+@Service
+public class BasicFieldServiceImpl extends ServiceImpl<BasicFieldMapper, BasicField> implements BasicFieldService {
+
+    @Override
+    public List<BasicField> listBySchoolId(Long schoolId) {
+        QueryWrapper<BasicField> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(BasicField::getSchoolId, schoolId)
+                .orderByAsc(BasicField::getId);
+        return this.list(queryWrapper);
+    }
+
+    @Override
+    public List<BasicField> listData() {
+        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
+        List<BasicField> basicFieldList = this.listBySchoolId(schoolId);
+        if (CollectionUtils.isNotEmpty(basicFieldList)) {
+            return basicFieldList;
+        } else {
+            List<BasicField> basicFields = BasicFieldEnum.list(schoolId);
+            this.saveBatch(basicFields);
+            return basicFields;
+        }
+    }
+
+    @Override
+    public boolean savaData(List<BasicField> list) {
+        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
+        SysUser sysUser = (SysUser) ServletUtil.getRequestHeaderUserId();
+        // 校验code是否重复
+        StringJoiner stringJoiner = new StringJoiner(";");
+        Map<String, List<BasicField>> codeMap = list.stream().collect(Collectors.groupingBy(BasicField::getCode));
+        for (Map.Entry<String, List<BasicField>> entry : codeMap.entrySet()) {
+            if (entry.getValue().size() > 1) {
+                stringJoiner.add(String.format("字段代码[%s]重复", entry.getKey()));
+            }
+        }
+        // 校验name是否重复
+        Map<String, List<BasicField>> nameMap = list.stream().collect(Collectors.groupingBy(BasicField::getName));
+        for (Map.Entry<String, List<BasicField>> entry : nameMap.entrySet()) {
+            if (entry.getValue().size() > 1) {
+                stringJoiner.add(String.format("字段名称[%s]重复", entry.getKey()));
+            }
+        }
+        if (StringUtils.isNotBlank(stringJoiner.toString())) {
+            throw ExceptionResultEnum.ERROR.exception(stringJoiner.toString());
+        }
+        // 保存
+        if (this.removeBySchoolId(schoolId)) {
+            for (BasicField basicField : list) {
+                basicField.setId(SystemConstant.getDbUuid());
+                basicField.setSchoolId(schoolId);
+                basicField.setCreateId(sysUser.getId());
+                basicField.setCreateTime(System.currentTimeMillis());
+            }
+            return this.saveBatch(list);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean removeBySchoolId(Long schoolId) {
+        QueryWrapper<BasicField> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(BasicField::getSchoolId, schoolId);
+        return this.remove(queryWrapper);
+    }
+}

+ 20 - 0
paper-library-common/src/main/resources/mapper/BasicFieldMapper.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qmth.paper.library.common.mapper.BasicFieldMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.qmth.paper.library.common.entity.BasicField">
+        <id column="id" property="id" />
+        <result column="school_id" property="schoolId" />
+        <result column="code" property="code" />
+        <result column="name" property="name" />
+        <result column="enable" property="enable" />
+        <result column="disable" property="disable" />
+        <result column="basic_field" property="basicField" />
+        <result column="create_id" property="createId" />
+        <result column="create_time" property="createTime" />
+        <result column="update_id" property="updateId" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+</mapper>

+ 45 - 0
paper-library/src/main/java/com/qmth/paper/library/api/BasicFieldController.java

@@ -0,0 +1,45 @@
+package com.qmth.paper.library.api;
+
+
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.paper.library.common.entity.BasicField;
+import com.qmth.paper.library.common.service.BasicFieldService;
+import com.qmth.paper.library.common.contant.ApiPrefixConstant;
+import com.qmth.paper.library.common.util.Result;
+import com.qmth.paper.library.common.util.ResultUtil;
+import io.swagger.annotations.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * <p>
+ * 字段管理 前端控制器
+ * </p>
+ */
+@Api(tags = "字段管理Controller")
+@RestController
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + ApiPrefixConstant.PREFIX_BASIC)
+@Validated
+public class BasicFieldController {
+
+    @Resource
+    private BasicFieldService basicFieldService;
+
+    @ApiOperation(value = "查询")
+    @PostMapping("/field/list")
+    @ApiResponses({@ApiResponse(code = 200, message = "查询成功", response = Result.class)})
+    public Result list() {
+        return ResultUtil.ok(basicFieldService.listData());
+    }
+
+    @ApiOperation(value = "保存")
+    @PostMapping("/field/save")
+    @ApiResponses({@ApiResponse(code = 200, message = "保存成功", response = Result.class)})
+    public Result pictureUpload(@RequestBody List<BasicField> list) {
+        return ResultUtil.ok(basicFieldService.savaData(list));
+    }
+
+}

+ 13 - 5
paper-library/src/main/java/com/qmth/paper/library/api/ExamStudentController.java

@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import javax.validation.constraints.Max;
 import javax.validation.constraints.Min;
@@ -32,7 +33,7 @@ import java.util.List;
 @Validated
 public class ExamStudentController {
     @Resource
-    private ExamStudentService basicStudentService;
+    private ExamStudentService examStudentService;
 
     @ApiOperation(value = "查询")
     @RequestMapping(value = "/page", method = RequestMethod.POST)
@@ -45,7 +46,7 @@ public class ExamStudentController {
                                        @ApiParam(value = "查询参数(学生学号或姓名)") @RequestParam(required = false) String queryParams,
                                        @ApiParam(value = "分页页码", required = true) @RequestParam @Min(SystemConstant.PAGE_NUMBER_MIN) Integer pageNumber,
                                        @ApiParam(value = "分页数", required = true) @RequestParam @Min(SystemConstant.PAGE_SIZE_MIN) @Max(SystemConstant.PAGE_SIZE_MAX) Integer pageSize) {
-        return ResultUtil.ok(basicStudentService.findStudentPage(examId, courseCode, collegeName, majorName, className, queryParams, pageNumber, pageSize));
+        return ResultUtil.ok(examStudentService.findStudentPage(examId, courseCode, collegeName, majorName, className, queryParams, pageNumber, pageSize));
     }
 
     @ApiOperation(value = "新增/编辑")
@@ -55,7 +56,7 @@ public class ExamStudentController {
         if (bindingResult.hasErrors()) {
             return ResultUtil.error(bindingResult.getAllErrors().get(0).getDefaultMessage());
         }
-        basicStudentService.saveBasicStudent(basicStudent);
+        examStudentService.saveBasicStudent(basicStudent);
         return ResultUtil.ok(true, "");
     }
 
@@ -63,7 +64,14 @@ public class ExamStudentController {
     @RequestMapping(value = "/delete", method = RequestMethod.POST)
     @ApiResponses({@ApiResponse(code = 200, message = "删除成功", response = EditResult.class)})
     public Result deleteBasicStudent(@RequestParam List<Long> ids) {
-        return ResultUtil.ok(basicStudentService.removeBasicStudentBatch(ids));
+        return ResultUtil.ok(examStudentService.removeBasicStudentBatch(ids));
+    }
+
+    @ApiOperation(value = "导入模板下载")
+    @RequestMapping(value = "/download_template", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "返回信息", response = EditResult.class)})
+    public void downloadTemplate(HttpServletResponse response){
+        examStudentService.downloadTemplate(response);
     }
 
     @ApiOperation(value = "导入")
@@ -72,7 +80,7 @@ public class ExamStudentController {
     public Result basicStudentImportAsync(@ApiParam(value = "学期ID", required = true) @RequestParam Long semesterId,
                                           @ApiParam(value = "考试ID", required = true) @RequestParam Long examId,
                                           @ApiParam(value = "上传文件", required = true) @RequestParam MultipartFile file) throws Exception {
-        basicStudentService.dataImport(semesterId, examId, file);
+        examStudentService.dataImport(semesterId, examId, file);
         return ResultUtil.ok(true, "");
     }
 

+ 63 - 0
paper-library/src/main/resources/db-log/xf.sql

@@ -0,0 +1,63 @@
+CREATE TABLE `basic_field` (
+     `id` BIGINT(20) NOT NULL COMMENT '主键',
+     `school_id` BIGINT(20) NOT NULL COMMENT '学校ID',
+     `code` VARCHAR(45) NOT NULL COMMENT '字段代码',
+     `name` VARCHAR(45) NOT NULL COMMENT '字段名称',
+     `enable` TINYINT(1) NOT NULL COMMENT '是否选中',
+     `disable` TINYINT(1) NOT NULL COMMENT '是否禁用',
+     `basic_field` TINYINT(1) NOT NULL COMMENT '是否基础字段',
+     `create_id` BIGINT(20) NULL,
+     `create_time` BIGINT(20) NULL,
+     `update_id` BIGINT(20) NULL,
+     `update_time` BIGINT(20) NULL,
+     PRIMARY KEY (`id`))
+    COMMENT = '字段管理表';
+
+ALTER TABLE `t_b_task` CHANGE COLUMN `org_id` `exam_id` BIGINT NULL DEFAULT NULL COMMENT '考试ID' ;
+ALTER TABLE `exam_student` ADD COLUMN `extend_fields` MEDIUMTEXT NULL COMMENT '扩展字段' AFTER `remark`;
+ALTER TABLE `t_b_task` CHANGE COLUMN `txt_file_path` `error_file_path` VARCHAR(500) CHARACTER SET 'utf8mb4' NULL DEFAULT NULL COMMENT '错误文件路径' ;
+
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `enable`, `default_auth`, `front_display`) VALUES ('273', '字段管理', 'FieldManage', 'MENU', '8', '2', '1', '0', '1');
+UPDATE `sys_privilege` SET `sequence` = '4' WHERE (`id` = '510');
+UPDATE `sys_privilege` SET `sequence` = '5' WHERE (`id` = '9');
+UPDATE `sys_privilege` SET `sequence` = '6' WHERE (`id` = '20');
+UPDATE `sys_privilege` SET `sequence` = '10' WHERE (`id` = '549');
+
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('274', '查询', '/api/admin/basic/field/list', 'URL', '273', '1', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('275', '保存', '/api/admin/basic/field/save', 'URL', '273', '2', 'AUTH', '1', '1', '1');
+UPDATE `sys_privilege` SET `related` = '274,275' WHERE (`id` = '273');
+
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('33', '模板下载', '/api/admin/exam/student/download_template', 'URL', '20', '5', 'AUTH', '1', '1', '1');
+UPDATE `sys_privilege` SET `related` = '32,33' WHERE (`id` = '26');
+
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `enable`, `default_auth`, `front_display`) VALUES ('276', '其他文件类型管理', 'FiletypeMange', 'MENU', '8', '3', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('277', '新建', 'Add', 'BUTTON', '276', '1', 'AUTH', '281', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('278', '删除', 'Delete', 'LINK', '276', '2', 'AUTH', '282', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('279', '列表', 'List', 'LIST', '276', '3', 'AUTH', '280', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('280', '查询', '/api/admin/basic/filetype/page', 'URL', '276', '1', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('281', '新增', '/api/admin/basic/filetype/save', 'URL', '276', '2', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('282', '删除', '/api/admin/basic/filetype/delete', 'URL', '276', '3', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('283', '查询列表', '/api/admin/basic/filetype/list', 'URL', '276', '4', 'AUTH', '1', '1', '1');
+
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('284', '扫描统计管理', 'ScanStatManage', 'MENU', '8', '7', 'AUTH', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('285', '列表', 'List', 'LIST', '284', '1', 'AUTH', '289', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('286', '查询条件', 'Condition', 'CONDITION', '284', '2', 'AUTH', '289', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('287', '查询', 'Select', 'BUTTON', '284', '3', 'AUTH', '289', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('288', '导出', 'Export', 'BUTTON', '284', '4', 'AUTH', '290', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('289', '查询', '/api/admin/basic/scan/stat/page', 'URL', '284', '1', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('290', '导出', '/api/admin/basic/scan/stat/export', 'URL', '284', '2', 'AUTH', '1', '1', '1');
+
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `enable`, `default_auth`, `front_display`) VALUES ('300', '图片管理', 'PaperPictureManage', 'MENU', '18', '2', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('301', '查询条件', 'Condition', 'CONDITION', '300', '1', 'AUTH', '306', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('302', '列表', 'List', 'LIST', '300', '2', 'AUTH', '306', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('303', '查询', 'Select', 'BUTTON', '300', '3', 'AUTH', '306', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('304', '下载图片', 'DownloadPicture', 'BUTTON', '300', '4', 'AUTH', '307', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('305', '图片详情', 'PictureDetail', 'LINK', '300', '5', 'AUTH', '308,309', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('306', '查询', '/api/admin/paper/picture/page', 'URL', '300', '1', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('307', '下载图片', '/api/admin/paper/picture/download', 'URL', '300', '2', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('308', '查看考生图片', '/api/admin/paper/picture/list_student_picture', 'URL', '300', '3', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('309', '保存考生图片', '/api/admin/paper/picture/save', 'URL', '300', '4', 'AUTH', '1', '1', '1');
+
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `enable`, `default_auth`, `front_display`) VALUES ('205', '根据考号获取考生信息', '/api/admin/client/student/get', 'URL', '200', '5', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `enable`, `default_auth`, `front_display`) VALUES ('206', '获取考生/班级', '/api/admin/client/classroom/get', 'URL', '200', '6', '1', '1', '1');
+UPDATE `sys_privilege` SET `related` = '30,202,203,204,205,206,281,283' WHERE (`id` = '200');