Kaynağa Gözat

违纪、考生状态导入

xiatian 10 ay önce
ebeveyn
işleme
e895d75270

+ 29 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/StudentController.java

@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
 
 import com.qmth.boot.api.annotation.Aac;
 import com.qmth.boot.api.constant.ApiConstant;
@@ -22,6 +23,8 @@ import com.qmth.boot.tools.iterator.PageListIterator;
 
 import cn.com.qmth.scancentral.controller.BaseController;
 import cn.com.qmth.scancentral.service.StudentService;
+import cn.com.qmth.scancentral.util.ResouceUtil;
+import cn.com.qmth.scancentral.vo.ImportBreachResult;
 import cn.com.qmth.scancentral.vo.student.StudentAnswerVo;
 import cn.com.qmth.scancentral.vo.student.StudentPageQuery;
 import cn.com.qmth.scancentral.vo.student.StudentPageVo;
@@ -69,4 +72,30 @@ public class StudentController extends BaseController {
         writer.output(response.getOutputStream());
 
     }
+
+    @ApiOperation(value = "违纪导入模版下载")
+    @PostMapping("breach/template")
+    public void breachTemplate(HttpServletResponse response) {
+        exportFile("违纪导入模板.xlsx", ResouceUtil.getStream("templates/breachImport.xlsx"));
+    }
+
+    @ApiOperation(value = "违纪导入")
+    @PostMapping("breach/import")
+    public ImportBreachResult breachImport(@RequestParam Long examId, @RequestParam String subjectCode,
+            @RequestParam MultipartFile file) {
+        return studentService.breachImport(examId, subjectCode, file);
+    }
+
+    @ApiOperation(value = "考生状态导入模版下载")
+    @PostMapping("cust-status/template")
+    public void custStatusTemplate(HttpServletResponse response) {
+        exportFile("考生状态导入模板.xlsx", ResouceUtil.getStream("templates/custStatusImport.xlsx"));
+    }
+
+    @ApiOperation(value = "违纪导入")
+    @PostMapping("cust-status/import")
+    public ImportBreachResult custStatusImport(@RequestParam Long examId, @RequestParam String subjectCode,
+            @RequestParam MultipartFile file) {
+        return studentService.custStatusImport(examId, subjectCode, file);
+    }
 }

+ 15 - 0
src/main/java/cn/com/qmth/scancentral/controller/admin/SubjectController.java

@@ -1,5 +1,7 @@
 package cn.com.qmth.scancentral.controller.admin;
 
+import java.util.List;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -12,6 +14,8 @@ import com.qmth.boot.api.constant.ApiConstant;
 import cn.com.qmth.scancentral.controller.BaseController;
 import cn.com.qmth.scancentral.service.SubjectService;
 import cn.com.qmth.scancentral.vo.subject.ScanProgressVo;
+import cn.com.qmth.scancentral.vo.subject.SubjectBreachVo;
+import cn.com.qmth.scancentral.vo.subject.SubjectCustStatusVo;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 
@@ -30,4 +34,15 @@ public class SubjectController extends BaseController {
         return subjectService.scanProgress(examId, subjectCode);
     }
 
+    @ApiOperation(value = "查询科目违纪导入")
+    @PostMapping("breach/list")
+    public List<SubjectBreachVo> breachList(@RequestParam Long examId) {
+        return subjectService.breachList(examId);
+    }
+
+    @ApiOperation(value = "查询科目考生状态导入")
+    @PostMapping("cust-status/list")
+    public List<SubjectCustStatusVo> custStatusList(@RequestParam Long examId) {
+        return subjectService.custStatusList(examId);
+    }
 }

+ 1 - 0
src/main/java/cn/com/qmth/scancentral/dao/StudentDao.java

@@ -101,4 +101,5 @@ public interface StudentDao extends BaseMapper<StudentEntity> {
 
     IPage<StudentExamRoomVo> studentExamRoomExport(Page<StudentExamRoomVo> page,
             @Param("query") AnswerQueryDomain query);
+
 }

+ 6 - 2
src/main/java/cn/com/qmth/scancentral/enums/FileType.java

@@ -7,8 +7,12 @@ public enum FileType {
 
     SHEET("原图", "%d/answer/%d/%s/%d-%d.%s"), SLICE("裁切图", "%d/answer/%d/%s/%d-%d-%d.%s"), PACKAGE("签到表",
             "%d/card/package.%s"), CARD("题卡", "%d/card/answer/%d.%s"), ADMIN_SLICE("管理员上传裁切图",
-                    "%d/upload/%s/%d-%d-%d/%s.%s"), ADMIN_SHEET("管理员上传裁原图",
-                            "%d/upload/%s/%d-%d/%s.%s"), ADAPTE_FILE("适配题卡", "%d/card/answer/%d/%s.%s"),;
+                    "%d/upload/%s/%d-%d-%d/%s.%s"), ADMIN_SHEET("管理员上传裁原图", "%d/upload/%s/%d-%d/%s.%s"), ADAPTE_FILE(
+                            "适配题卡", "%d/card/answer/%d/%s.%s"), BREACH_FILE("违纪文件",
+                                    "%d/breach/%s/breach.xlsx"), BREACH_INFO_FILE("违纪信息文件",
+                                            "%d/breach-info/%s/breach-info.txt"), CUST_STATUS_FILE("考生状态文件",
+                                                    "%d/cust-status/%s/cust-status.xlsx"), CUST_STATUS_INFO_FILE(
+                                                            "考生状态信息文件", "%d/cust-status-info/%s/cust-status-info.txt"),;
 
     private String name;
 

+ 16 - 0
src/main/java/cn/com/qmth/scancentral/service/FileService.java

@@ -35,4 +35,20 @@ public interface FileService {
     String adminUploadSheet(InputStream in, String md5, Long examId, String examNumber, Integer paperNumber,
             Integer pageIndex);
 
+    String getBreachUri(Long examId, String subjectCode);
+
+    String uploadBreach(InputStream in, String md5, Long examId, String subjectCode);
+
+    String getBreachInfoUri(Long examId, String subjectCode);
+
+    String uploadBreachInfo(InputStream in, String md5, Long examId, String subjectCode);
+
+    String uploadCustStatusInfo(InputStream in, String md5, Long examId, String subjectCode);
+
+    String getCustStatusInfoUri(Long examId, String subjectCode);
+
+    String uploadCustStatus(InputStream in, String md5, Long examId, String subjectCode);
+
+    String getCustStatusUri(Long examId, String subjectCode);
+
 }

+ 47 - 11
src/main/java/cn/com/qmth/scancentral/service/StudentService.java

@@ -1,13 +1,53 @@
 package cn.com.qmth.scancentral.service;
 
-import cn.com.qmth.scancentral.bean.*;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.boot.core.collection.PageResult;
+
+import cn.com.qmth.scancentral.bean.AbsentQueryDomain;
+import cn.com.qmth.scancentral.bean.AnswerDeleteDomain;
+import cn.com.qmth.scancentral.bean.AnswerQueryDomain;
+import cn.com.qmth.scancentral.bean.AssignedQueryDomain;
+import cn.com.qmth.scancentral.bean.ImportCetAbsentDomain;
+import cn.com.qmth.scancentral.bean.ImportStudentDomain;
+import cn.com.qmth.scancentral.bean.PageDeleteDomain;
+import cn.com.qmth.scancentral.bean.User;
 import cn.com.qmth.scancentral.bean.omredit.OmrEditDomain;
 import cn.com.qmth.scancentral.bean.omredit.OmrFieldEditDomain;
 import cn.com.qmth.scancentral.bean.refix.AnswerRefixDomain;
 import cn.com.qmth.scancentral.entity.StudentEntity;
 import cn.com.qmth.scancentral.entity.StudentPaperEntity;
-import cn.com.qmth.scancentral.enums.*;
-import cn.com.qmth.scancentral.vo.*;
+import cn.com.qmth.scancentral.enums.ExamStatus;
+import cn.com.qmth.scancentral.enums.ExamStatusCheckMode;
+import cn.com.qmth.scancentral.enums.GroupType;
+import cn.com.qmth.scancentral.enums.OP;
+import cn.com.qmth.scancentral.enums.ScanStatus;
+import cn.com.qmth.scancentral.enums.UploadStatus;
+import cn.com.qmth.scancentral.vo.AbsentInfoVo;
+import cn.com.qmth.scancentral.vo.AbsentManualImportVo;
+import cn.com.qmth.scancentral.vo.AbsentQueryVo;
+import cn.com.qmth.scancentral.vo.AnswerDeleteVo;
+import cn.com.qmth.scancentral.vo.AnswerExportK12Vo;
+import cn.com.qmth.scancentral.vo.AnswerExportVo;
+import cn.com.qmth.scancentral.vo.AnswerRefixVo;
+import cn.com.qmth.scancentral.vo.CampusVo;
+import cn.com.qmth.scancentral.vo.ExamSiteVo;
+import cn.com.qmth.scancentral.vo.ExportCetMarkingQueryVo;
+import cn.com.qmth.scancentral.vo.ExportCetVo;
+import cn.com.qmth.scancentral.vo.ImportBreachResult;
+import cn.com.qmth.scancentral.vo.ImportResult;
+import cn.com.qmth.scancentral.vo.ImportStudentQueryVo;
+import cn.com.qmth.scancentral.vo.ImportStudentVo;
+import cn.com.qmth.scancentral.vo.PaperDeleteVo;
+import cn.com.qmth.scancentral.vo.ScanAnswerInfoVo;
+import cn.com.qmth.scancentral.vo.StudentUploadVo;
+import cn.com.qmth.scancentral.vo.UpdateTimeVo;
 import cn.com.qmth.scancentral.vo.answerquery.AnswerQueryVo;
 import cn.com.qmth.scancentral.vo.assginedcheck.AssginedTaskResult;
 import cn.com.qmth.scancentral.vo.assginedcheck.AssignedCheckExport;
@@ -22,14 +62,6 @@ import cn.com.qmth.scancentral.vo.student.StudentQuery;
 import cn.com.qmth.scancentral.vo.student.StudentVo;
 import cn.com.qmth.scancentral.vo.subject.SubjectScanProgressVo;
 import cn.com.qmth.scancentral.vo.task.TaskStatusVo;
-import com.baomidou.mybatisplus.extension.service.IService;
-import com.qmth.boot.core.collection.PageResult;
-import org.springframework.web.multipart.MultipartFile;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collection;
-import java.util.List;
 
 public interface StudentService extends IService<StudentEntity> {
 
@@ -187,4 +219,8 @@ public interface StudentService extends IService<StudentEntity> {
 
     UpdateTimeVo omrFieldEdit(User accessUser, OmrFieldEditDomain domain);
 
+    ImportBreachResult breachImport(Long examId, String subjectCode, MultipartFile file);
+
+    ImportBreachResult custStatusImport(Long examId, String subjectCode, MultipartFile file);
+
 }

+ 6 - 0
src/main/java/cn/com/qmth/scancentral/service/SubjectService.java

@@ -11,6 +11,8 @@ import cn.com.qmth.scancentral.entity.SubjectEntity;
 import cn.com.qmth.scancentral.vo.SubjectConfigVo;
 import cn.com.qmth.scancentral.vo.examinfo.SubjectConfig;
 import cn.com.qmth.scancentral.vo.subject.ScanProgressVo;
+import cn.com.qmth.scancentral.vo.subject.SubjectBreachVo;
+import cn.com.qmth.scancentral.vo.subject.SubjectCustStatusVo;
 
 public interface SubjectService extends IMppService<SubjectEntity> {
 
@@ -30,4 +32,8 @@ public interface SubjectService extends IMppService<SubjectEntity> {
 
     ScanProgressVo scanProgress(Long examId, String subjectCode);
 
+    List<SubjectBreachVo> breachList(Long examId);
+
+    List<SubjectCustStatusVo> custStatusList(Long examId);
+
 }

+ 100 - 0
src/main/java/cn/com/qmth/scancentral/service/impl/FileServiceImpl.java

@@ -51,6 +51,106 @@ public class FileServiceImpl implements FileService {
                 FormatType.JPG.name().toLowerCase());
     }
 
+    @Override
+    public String getBreachUri(Long examId, String subjectCode) {
+        return FileType.BREACH_FILE.getPath(examId, subjectCode);
+    }
+
+    @Override
+    public String uploadBreach(InputStream in, String md5, Long examId, String subjectCode) {
+        try {
+            String path = getBreachUri(examId, subjectCode);
+            fileStore.write(path, in, md5);
+            return path;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new StatusException("文件上传出错", e);
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getBreachInfoUri(Long examId, String subjectCode) {
+        return FileType.BREACH_FILE.getPath(examId, subjectCode);
+    }
+
+    @Override
+    public String uploadBreachInfo(InputStream in, String md5, Long examId, String subjectCode) {
+        try {
+            String path = getBreachInfoUri(examId, subjectCode);
+            fileStore.write(path, in, md5);
+            return path;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new StatusException("文件上传出错", e);
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getCustStatusUri(Long examId, String subjectCode) {
+        return FileType.CUST_STATUS_FILE.getPath(examId, subjectCode);
+    }
+
+    @Override
+    public String uploadCustStatus(InputStream in, String md5, Long examId, String subjectCode) {
+        try {
+            String path = getCustStatusUri(examId, subjectCode);
+            fileStore.write(path, in, md5);
+            return path;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new StatusException("文件上传出错", e);
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getCustStatusInfoUri(Long examId, String subjectCode) {
+        return FileType.CUST_STATUS_INFO_FILE.getPath(examId, subjectCode);
+    }
+
+    @Override
+    public String uploadCustStatusInfo(InputStream in, String md5, Long examId, String subjectCode) {
+        try {
+            String path = getCustStatusInfoUri(examId, subjectCode);
+            fileStore.write(path, in, md5);
+            return path;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new StatusException("文件上传出错", e);
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
     @Override
     public String getAdminSheetUri(Long examId, String examNumber, Integer paperNumber, Integer pageIndex) {
         return FileType.ADMIN_SHEET.getPath(examId, examNumber, paperNumber, pageIndex, FastUUID.get(),

+ 276 - 0
src/main/java/cn/com/qmth/scancentral/service/impl/StudentServiceImpl.java

@@ -1,14 +1,17 @@
 package cn.com.qmth.scancentral.service.impl;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.LineNumberReader;
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -18,6 +21,7 @@ import java.util.stream.Collectors;
 import javax.validation.constraints.NotNull;
 
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -43,6 +47,7 @@ import com.qmth.boot.tools.excel.ExcelReader;
 import com.qmth.boot.tools.excel.enums.ExcelType;
 import com.qmth.boot.tools.io.IOUtils;
 import com.qmth.boot.tools.signature.SignatureType;
+import com.qmth.boot.tools.uuid.FastUUID;
 
 import cn.com.qmth.scancentral.bean.AbsentQueryDomain;
 import cn.com.qmth.scancentral.bean.AnswerDeleteDomain;
@@ -93,6 +98,7 @@ import cn.com.qmth.scancentral.service.AnswerCardSubjectService;
 import cn.com.qmth.scancentral.service.AssignedCheckHistoryService;
 import cn.com.qmth.scancentral.service.BatchService;
 import cn.com.qmth.scancentral.service.ExamService;
+import cn.com.qmth.scancentral.service.FileService;
 import cn.com.qmth.scancentral.service.OmrGroupService;
 import cn.com.qmth.scancentral.service.OmrTaskService;
 import cn.com.qmth.scancentral.service.PaperPageService;
@@ -108,6 +114,7 @@ import cn.com.qmth.scancentral.support.TaskLock;
 import cn.com.qmth.scancentral.support.TaskLockUtil;
 import cn.com.qmth.scancentral.util.BatchGetDataUtil;
 import cn.com.qmth.scancentral.util.BatchSetDataUtil;
+import cn.com.qmth.scancentral.util.MD5Util;
 import cn.com.qmth.scancentral.util.PageUtil;
 import cn.com.qmth.scancentral.vo.AbsentInfoVo;
 import cn.com.qmth.scancentral.vo.AbsentManualImportVo;
@@ -120,6 +127,7 @@ import cn.com.qmth.scancentral.vo.CampusVo;
 import cn.com.qmth.scancentral.vo.ExamSiteVo;
 import cn.com.qmth.scancentral.vo.ExportCetMarkingQueryVo;
 import cn.com.qmth.scancentral.vo.ExportCetVo;
+import cn.com.qmth.scancentral.vo.ImportBreachResult;
 import cn.com.qmth.scancentral.vo.ImportResult;
 import cn.com.qmth.scancentral.vo.ImportStudentQueryVo;
 import cn.com.qmth.scancentral.vo.ImportStudentVo;
@@ -151,6 +159,10 @@ import cn.com.qmth.scancentral.vo.task.TaskStatusVo;
 @Service
 public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService {
 
+    private static final String[] BREACH_EXCEL_HEADER = new String[] { "准考证号", "违纪码" };
+
+    private static final String[] CUST_STATUS_EXCEL_HEADER = new String[] { "准考证号", "考生状态" };
+
     private static final Logger log = LoggerFactory.getLogger(StudentService.class);
 
     @Autowired
@@ -204,6 +216,9 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
     @Autowired
     private UserService userService;
 
+    @Autowired
+    private FileService fileService;
+
     /**
      * 整体更新考生绑定的paper并刷新考生状态,若集合为空则表示考生无扫描结果,需要在外部调用处对考生上锁
      *
@@ -2376,4 +2391,265 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
         return baseMapper.studentExamRoomExport(new Page<>(query.getPageNumber(), query.getPageSize()), query)
                 .getRecords();
     }
+
+    @Override
+    public ImportBreachResult breachImport(Long examId, String subjectCode, MultipartFile file) {
+        // 暂存临时文件
+        File temDir = new File("temp/" + FastUUID.get() + "/");
+        try {
+            temDir.mkdirs();
+            File excel = new File(temDir.getAbsolutePath() + "/breach.xlsx");
+            try {
+                file.transferTo(excel);
+            } catch (Exception e) {
+                throw new RuntimeException("系统错误", e);
+            }
+            // 校验Excel
+            ImportBreachResult ret = null;
+            InputStream in = null;
+            try {
+                in = new FileInputStream(excel);
+                ret = breachImportCheck(examId, subjectCode, in);
+            } catch (IOException e) {
+                throw new RuntimeException("系统错误", e);
+            } finally {
+                if (in != null) {
+                    try {
+                        in.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+            // 根据校验结果上传文件
+            if (ret != null && ret.getSuccess()) {
+                InputStream excelIn = null;
+                try {
+                    String md5 = MD5Util.md5Hex(excel);
+                    excelIn = new FileInputStream(excel);
+                    fileService.uploadBreach(excelIn, md5, examId, subjectCode);
+                } catch (IOException e) {
+                    throw new RuntimeException("系统错误", e);
+                } finally {
+                    if (excelIn != null) {
+                        try {
+                            excelIn.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+                File info = new File(temDir.getAbsolutePath() + "/breach-info.txt");
+                InputStream infoIn = null;
+                try {
+                    FileUtils.write(info, ret.getBreachCount().toString(), "utf-8");
+                    String md5 = MD5Util.md5Hex(info);
+                    infoIn = new FileInputStream(info);
+                    fileService.uploadBreachInfo(infoIn, md5, examId, subjectCode);
+                } catch (IOException e) {
+                    throw new RuntimeException("系统错误", e);
+                } finally {
+                    if (infoIn != null) {
+                        try {
+                            infoIn.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+            }
+            return ret;
+        } finally {
+            try {
+                FileUtils.deleteDirectory(temDir);
+            } catch (IOException e) {
+                throw new RuntimeException("系统错误", e);
+            }
+        }
+    }
+
+    private ImportBreachResult breachImportCheck(Long examId, String subjectCode, InputStream in) {
+        List<String[]> lineList = null;
+        ExcelReader reader = ExcelReader.create(ExcelType.XLSX, in, 0);
+        try {
+            lineList = reader.getDataArrayList();
+        } catch (Exception e) {
+            throw new ParameterException("Excel 解析失败");
+        }
+        if (!Arrays.equals(BREACH_EXCEL_HEADER, reader.getColumnNames())) {
+            throw new ParameterException("Excel表头错误");
+        }
+        if (CollectionUtils.isEmpty(lineList)) {
+            throw new ParameterException("Excel无内容");
+        }
+        if (500001 < lineList.size()) {
+            throw new ParameterException("数据行数不能超过500000");
+        }
+        ImportBreachResult ret = new ImportBreachResult();
+        List<String> failRecords = new ArrayList<>();
+        ret.setErrMsg(failRecords);
+        Set<String> examNumberSet = new HashSet<>();
+        for (int i = 0; i < lineList.size(); i++) {
+            String[] line = lineList.get(i);
+
+            StringBuilder msg = new StringBuilder();
+
+            String examNumber = trimAndNullIfBlank(line[0]);
+            if (StringUtils.isBlank(examNumber)) {
+                msg.append("  准考证号不能为空");
+            } else {
+                StudentEntity s = findByExamAndSubjectCodeAndExamNumber(examId, subjectCode, examNumber);
+                if (s == null) {
+                    msg.append("  准考证号未找到");
+                } else {
+                    if (examNumberSet.contains(examNumber)) {
+                        msg.append("  准考证号重复");
+                    } else {
+                        examNumberSet.add(examNumber);
+                    }
+                }
+            }
+
+            String breach = trimAndNullIfBlank(line[1]);
+            if (StringUtils.isNotBlank(breach)) {
+                ret.setBreachCount(ret.getBreachCount() + 1);
+            }
+
+        }
+
+        if (CollectionUtils.isNotEmpty(failRecords)) {
+            ret.setSuccess(false);
+            return ret;
+        }
+        ret.setSuccess(true);
+        return ret;
+    }
+
+    @Override
+    public ImportBreachResult custStatusImport(Long examId, String subjectCode, MultipartFile file) {
+        // 暂存临时文件
+        File temDir = new File("temp/" + FastUUID.get() + "/");
+        try {
+            temDir.mkdirs();
+            File excel = new File(temDir.getAbsolutePath() + "/cust-status.xlsx");
+            try {
+                file.transferTo(excel);
+            } catch (Exception e) {
+                throw new RuntimeException("系统错误", e);
+            }
+            // 校验Excel
+            ImportBreachResult ret = null;
+            InputStream in = null;
+            try {
+                in = new FileInputStream(excel);
+                ret = custStatusImportCheck(examId, subjectCode, in);
+            } catch (IOException e) {
+                throw new RuntimeException("系统错误", e);
+            } finally {
+                if (in != null) {
+                    try {
+                        in.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+            // 根据校验结果上传文件
+            if (ret != null && ret.getSuccess()) {
+                InputStream excelIn = null;
+                try {
+                    String md5 = MD5Util.md5Hex(excel);
+                    excelIn = new FileInputStream(excel);
+                    fileService.uploadCustStatus(in, md5, examId, subjectCode);
+                } catch (IOException e) {
+                    throw new RuntimeException("系统错误", e);
+                } finally {
+                    if (excelIn != null) {
+                        try {
+                            excelIn.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+                File info = new File(temDir.getAbsolutePath() + "/cust-status-info.txt");
+                InputStream infoIn = null;
+                try {
+                    FileUtils.write(info, ret.getBreachCount().toString(), "utf-8");
+                    String md5 = MD5Util.md5Hex(info);
+                    infoIn = new FileInputStream(info);
+                    fileService.uploadCustStatusInfo(infoIn, md5, examId, subjectCode);
+                } catch (IOException e) {
+                    throw new RuntimeException("系统错误", e);
+                } finally {
+                    if (infoIn != null) {
+                        try {
+                            infoIn.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+            }
+            return ret;
+        } finally {
+            try {
+                FileUtils.deleteDirectory(temDir);
+            } catch (IOException e) {
+                throw new RuntimeException("系统错误", e);
+            }
+        }
+    }
+
+    private ImportBreachResult custStatusImportCheck(Long examId, String subjectCode, InputStream in) {
+        List<String[]> lineList = null;
+        ExcelReader reader = ExcelReader.create(ExcelType.XLSX, in, 0);
+        try {
+            lineList = reader.getDataArrayList();
+        } catch (Exception e) {
+            throw new ParameterException("Excel 解析失败");
+        }
+        if (!Arrays.equals(CUST_STATUS_EXCEL_HEADER, reader.getColumnNames())) {
+            throw new ParameterException("Excel表头错误");
+        }
+        if (CollectionUtils.isEmpty(lineList)) {
+            throw new ParameterException("Excel无内容");
+        }
+        if (500001 < lineList.size()) {
+            throw new ParameterException("数据行数不能超过500000");
+        }
+        ImportBreachResult ret = new ImportBreachResult();
+        List<String> failRecords = new ArrayList<>();
+        ret.setErrMsg(failRecords);
+        Set<String> examNumberSet = new HashSet<>();
+        for (int i = 0; i < lineList.size(); i++) {
+            String[] line = lineList.get(i);
+
+            StringBuilder msg = new StringBuilder();
+
+            String examNumber = trimAndNullIfBlank(line[0]);
+            if (StringUtils.isBlank(examNumber)) {
+                msg.append("  准考证号不能为空");
+            } else {
+                StudentEntity s = findByExamAndSubjectCodeAndExamNumber(examId, subjectCode, examNumber);
+                if (s == null) {
+                    msg.append("  准考证号未找到");
+                } else {
+                    if (examNumberSet.contains(examNumber)) {
+                        msg.append("  准考证号重复");
+                    } else {
+                        examNumberSet.add(examNumber);
+                    }
+                }
+            }
+
+            String breach = trimAndNullIfBlank(line[1]);
+            if (StringUtils.isNotBlank(breach)) {
+                ret.setBreachCount(ret.getBreachCount() + 1);
+            }
+
+        }
+
+        if (CollectionUtils.isNotEmpty(failRecords)) {
+            ret.setSuccess(false);
+            return ret;
+        }
+        ret.setSuccess(true);
+        return ret;
+    }
+
 }

+ 61 - 0
src/main/java/cn/com/qmth/scancentral/service/impl/SubjectServiceImpl.java

@@ -17,6 +17,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.github.jeffreyning.mybatisplus.service.MppServiceImpl;
 import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.core.fss.store.FileStore;
 
 import cn.com.qmth.scancentral.bean.ImportSubjectDomain;
 import cn.com.qmth.scancentral.bean.SubjectConfigDomain;
@@ -24,12 +25,16 @@ import cn.com.qmth.scancentral.bean.User;
 import cn.com.qmth.scancentral.dao.SubjectDao;
 import cn.com.qmth.scancentral.entity.SubjectEntity;
 import cn.com.qmth.scancentral.service.BatchPaperService;
+import cn.com.qmth.scancentral.service.FileService;
 import cn.com.qmth.scancentral.service.StudentService;
 import cn.com.qmth.scancentral.service.SubjectService;
 import cn.com.qmth.scancentral.util.Calculator;
+import cn.com.qmth.scancentral.util.FileUtil;
 import cn.com.qmth.scancentral.vo.SubjectConfigVo;
 import cn.com.qmth.scancentral.vo.examinfo.SubjectConfig;
 import cn.com.qmth.scancentral.vo.subject.ScanProgressVo;
+import cn.com.qmth.scancentral.vo.subject.SubjectBreachVo;
+import cn.com.qmth.scancentral.vo.subject.SubjectCustStatusVo;
 import cn.com.qmth.scancentral.vo.subject.SubjectScanProgressVo;
 
 @Service
@@ -38,6 +43,12 @@ public class SubjectServiceImpl extends MppServiceImpl<SubjectDao, SubjectEntity
     @Autowired
     private StudentService studentService;
 
+    @Autowired
+    private FileService fileService;
+
+    @Autowired
+    private FileStore fileStore;
+
     @Autowired
     private BatchPaperService batchPaperService;
 
@@ -177,4 +188,54 @@ public class SubjectServiceImpl extends MppServiceImpl<SubjectDao, SubjectEntity
         return ret;
     }
 
+    @Override
+    public List<SubjectBreachVo> breachList(Long examId) {
+        List<SubjectBreachVo> ret = new ArrayList<>();
+        List<SubjectEntity> subjects = listByExamId(examId);
+        if (CollectionUtils.isNotEmpty(subjects)) {
+            for (SubjectEntity e : subjects) {
+                SubjectBreachVo vo = new SubjectBreachVo();
+                ret.add(vo);
+                vo.setSubjectCode(e.getCode());
+                vo.setSubjectName(e.getName());
+                String url = fileService.getBreachInfoUri(examId, e.getCode());
+                try {
+                    if (fileStore.exist(url)) {
+                        vo.setBreachCount(Integer.valueOf(FileUtil.readFileContent(fileStore.read(url)).trim()));
+                    } else {
+                        vo.setBreachCount(0);
+                    }
+                } catch (Exception e1) {
+                    throw new RuntimeException("文件访问异常", e1);
+                }
+            }
+        }
+        return ret;
+    }
+
+    @Override
+    public List<SubjectCustStatusVo> custStatusList(Long examId) {
+        List<SubjectCustStatusVo> ret = new ArrayList<>();
+        List<SubjectEntity> subjects = listByExamId(examId);
+        if (CollectionUtils.isNotEmpty(subjects)) {
+            for (SubjectEntity e : subjects) {
+                SubjectCustStatusVo vo = new SubjectCustStatusVo();
+                ret.add(vo);
+                vo.setSubjectCode(e.getCode());
+                vo.setSubjectName(e.getName());
+                String url = fileService.getCustStatusInfoUri(examId, e.getCode());
+                try {
+                    if (fileStore.exist(url)) {
+                        vo.setCustStatusCount(Integer.valueOf(FileUtil.readFileContent(fileStore.read(url)).trim()));
+                    } else {
+                        vo.setCustStatusCount(0);
+                    }
+                } catch (Exception e1) {
+                    throw new RuntimeException("文件访问异常", e1);
+                }
+            }
+        }
+        return ret;
+    }
+
 }

+ 47 - 0
src/main/java/cn/com/qmth/scancentral/vo/ImportBreachResult.java

@@ -0,0 +1,47 @@
+package cn.com.qmth.scancentral.vo;
+
+import java.util.List;
+
+public class ImportBreachResult {
+
+    private Boolean success;
+
+    private Integer successCount = 0;
+
+    private Integer breachCount = 0;
+
+    private List<String> errMsg;
+
+    public Boolean getSuccess() {
+        return success;
+    }
+
+    public void setSuccess(Boolean success) {
+        this.success = success;
+    }
+
+    public Integer getSuccessCount() {
+        return successCount;
+    }
+
+    public void setSuccessCount(Integer successCount) {
+        this.successCount = successCount;
+    }
+
+    public List<String> getErrMsg() {
+        return errMsg;
+    }
+
+    public void setErrMsg(List<String> errMsg) {
+        this.errMsg = errMsg;
+    }
+
+    public Integer getBreachCount() {
+        return breachCount;
+    }
+
+    public void setBreachCount(Integer breachCount) {
+        this.breachCount = breachCount;
+    }
+
+}

+ 35 - 0
src/main/java/cn/com/qmth/scancentral/vo/subject/SubjectBreachVo.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.scancentral.vo.subject;
+
+public class SubjectBreachVo {
+
+    private String subjectCode;
+
+    private String subjectName;
+
+    private Integer breachCount;
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getSubjectName() {
+        return subjectName;
+    }
+
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
+    }
+
+    public Integer getBreachCount() {
+        return breachCount;
+    }
+
+    public void setBreachCount(Integer breachCount) {
+        this.breachCount = breachCount;
+    }
+
+}

+ 35 - 0
src/main/java/cn/com/qmth/scancentral/vo/subject/SubjectCustStatusVo.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.scancentral.vo.subject;
+
+public class SubjectCustStatusVo {
+
+    private String subjectCode;
+
+    private String subjectName;
+
+    private Integer custStatusCount;
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getSubjectName() {
+        return subjectName;
+    }
+
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
+    }
+
+    public Integer getCustStatusCount() {
+        return custStatusCount;
+    }
+
+    public void setCustStatusCount(Integer custStatusCount) {
+        this.custStatusCount = custStatusCount;
+    }
+
+}

BIN
src/main/resources/templates/breachImport.xlsx


BIN
src/main/resources/templates/custStatusImport.xlsx