Browse Source

考务数据导入

caozixuan 1 năm trước cách đây
mục cha
commit
12d4ed5bcc

+ 11 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/dto/ExaminationImportDto.java

@@ -83,6 +83,9 @@ public class ExaminationImportDto {
     @ApiModelProperty(value = "学校id")
     private Long schoolId;
 
+    @ApiModelProperty(value = "报错信息")
+    private String errorMessage;
+
     public ExaminationImportDto() {
     }
 
@@ -237,4 +240,12 @@ public class ExaminationImportDto {
     public void setClazzName(String clazzName) {
         this.clazzName = clazzName;
     }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
 }

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

@@ -300,19 +300,7 @@ public class ExamDetailServiceImpl extends ServiceImpl<ExamDetailMapper, ExamDet
         describeFont.setFontName("宋体");
         describeFont.setColor(IndexedColors.RED.getIndex());
         describeStyle.setFont(describeFont);
-        String describe = "说明\n" +
-                "1、【学号】必填;\n" +
-                "2、【座位号】必填;\n" +
-                "3、【姓名】必填;\n" +
-                "4、【课程代码】必填,且与命题任务中的课程代码相互对应;\n" +
-                "5、【课程名称】必填,且与命题任务中的课程名称相互对应;\n" +
-                "6、【试卷编号】必填,且与命题任务中的试卷编号相互对应;\n" +
-                "7、【考点】必填;\n" +
-                "8、【考场】必填;\n" +
-                "9、【考试日期】必填,且格式为YYYY-MM-DD。例如2023-07-09;\n" +
-                "10、【考试时间】必填,且格式为HH:mm-HH:mm。例如18:30-20:30;\n" +
-                "11、请不要删除此行,也不要删除模板中的任何列。\n" +
-                "12、使用前请先删除样例数据。\n";
+        String describe = SystemConstant.EXAMINATION_DESCRIBE;
         describe = describe.trim();
         XSSFRow rowDescribe = sheet.createRow(0);
         rowDescribe.setHeightInPoints(195); //行高设置成195px

+ 190 - 7
distributed-print-business/src/main/java/com/qmth/distributed/print/business/templete/execute/AsyncExaminationImportTemplateService.java

@@ -1,27 +1,45 @@
 package com.qmth.distributed.print.business.templete.execute;
 
 import cn.hutool.core.date.DateUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.qmth.boot.api.exception.ApiException;
-import com.qmth.distributed.print.business.service.ExamDetailService;
+import com.qmth.distributed.print.business.bean.dto.ExaminationImportDto;
+import com.qmth.distributed.print.business.bean.dto.FieldsDto;
 import com.qmth.distributed.print.business.service.ExamTaskService;
 import com.qmth.distributed.print.business.templete.importData.AsyncImportTaskTemplete;
 import com.qmth.distributed.print.business.templete.service.TaskLogicService;
+import com.qmth.teachcloud.common.config.DictionaryConfig;
 import com.qmth.teachcloud.common.contant.SpringContextHolder;
 import com.qmth.teachcloud.common.contant.SystemConstant;
 import com.qmth.teachcloud.common.entity.SysUser;
 import com.qmth.teachcloud.common.entity.TBTask;
-import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
-import com.qmth.teachcloud.common.enums.TaskResultEnum;
-import com.qmth.teachcloud.common.enums.TaskStatusEnum;
+import com.qmth.teachcloud.common.enums.*;
 import com.qmth.teachcloud.common.service.TBTaskService;
+import com.qmth.teachcloud.common.util.FileStoreUtil;
 import com.qmth.teachcloud.common.util.Result;
 import com.qmth.teachcloud.common.util.ResultUtil;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.text.MessageFormat;
 import java.util.*;
 
@@ -35,9 +53,6 @@ public class AsyncExaminationImportTemplateService extends AsyncImportTaskTemple
     @Resource
     private ExamTaskService examTaskService;
 
-    @Resource
-    private ExamDetailService examDetailService;
-
     private final static Logger log = LoggerFactory.getLogger(AsyncExaminationImportTemplateService.class);
 
     public static final String OBJ_TITLE = "考务数据";
@@ -59,6 +74,17 @@ public class AsyncExaminationImportTemplateService extends AsyncImportTaskTemple
             // 执行导入考务数据
             Map<String, Object> result = taskLogicService.executeImportExaminationLogic(map);
 
+            // 如果excel独立行数据校验有问题则生成excel
+            if (result.containsKey(SystemConstant.ERROR_DATA_LIST)) {
+                List<ExaminationImportDto> examinationImportDtoList = JSON.parseArray(JSON.toJSONString(result.get(SystemConstant.ERROR_DATA_LIST)), ExaminationImportDto.class);
+                List<FieldsDto> fieldsDtoList = JSON.parseArray(JSON.toJSONString(result.get("fieldsDtoList")), FieldsDto.class);
+
+                if (CollectionUtils.isNotEmpty(examinationImportDtoList) && CollectionUtils.isNotEmpty(fieldsDtoList)) {
+                    // 生成excel
+                    this.createLocalErrorExcel(examinationImportDtoList, fieldsDtoList, tbTask);
+                }
+            }
+
             // 检测是否去生成pdf
             if (Objects.isNull(map.get("examDetailIdList"))) {
                 throw ExceptionResultEnum.ERROR.exception("导入考务数据失败,数据未正确保存");
@@ -90,4 +116,161 @@ public class AsyncExaminationImportTemplateService extends AsyncImportTaskTemple
         }
         return ResultUtil.ok(map);
     }
+
+    /**
+     * 生成本地的excel错误信息文件
+     *
+     * @param examinationImportDtoList 包含错误信息的考务数据
+     * @param fieldsDtoList            属性字段集合
+     */
+    private void createLocalErrorExcel(List<ExaminationImportDto> examinationImportDtoList, List<FieldsDto> fieldsDtoList, TBTask tbTask) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        log.debug("导出Excel开始...");
+        XSSFWorkbook wb = new XSSFWorkbook();
+        XSSFSheet sheet = wb.createSheet("考务数据模板");
+
+        Font defaultFont = wb.createFont();
+        defaultFont.setFontHeightInPoints((short) 12);
+        defaultFont.setFontName("宋体");
+
+
+        int cellCount = fieldsDtoList.size() + 1;
+        // 说明
+        XSSFCellStyle describeStyle = wb.createCellStyle();
+        describeStyle.setAlignment(HorizontalAlignment.LEFT);
+        Font describeFont = wb.createFont();
+        describeFont.setFontHeightInPoints((short) 12);
+        describeFont.setFontName("宋体");
+        describeFont.setColor(IndexedColors.RED.getIndex());
+        describeStyle.setFont(describeFont);
+        String describe = SystemConstant.EXAMINATION_DESCRIBE;
+        describe = describe.trim();
+        XSSFRow rowDescribe = sheet.createRow(0);
+        rowDescribe.setHeightInPoints(195); //行高设置成195px
+        for (int i = 0; i < cellCount; i++) {
+            XSSFCell cell = rowDescribe.createCell(i);
+            if (i == 0) {
+                cell.setCellValue(describe);
+                cell.setCellStyle(describeStyle);
+            }
+        }
+        CellRangeAddress region = new CellRangeAddress(0, 0, 0, cellCount - 1);
+        sheet.addMergedRegion(region);
+
+        // 表头行
+        XSSFCellStyle headerStyle = wb.createCellStyle();
+        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        headerStyle.setAlignment(HorizontalAlignment.CENTER);
+        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        headerStyle.setFont(defaultFont);
+        XSSFRow rowHead = sheet.createRow(1);
+        for (int i = 0; i < fieldsDtoList.size(); i++) {
+            XSSFCell cell = rowHead.createCell(i);
+            cell.setCellValue(fieldsDtoList.get(i).getName());
+            cell.setCellStyle(headerStyle);
+        }
+        XSSFCell lastCell = rowHead.createCell(cellCount - 1);
+        lastCell.setCellValue("错误信息");
+        lastCell.setCellStyle(headerStyle);
+
+
+        // 内容行
+        XSSFCellStyle exampleStyle = wb.createCellStyle();
+        exampleStyle.setAlignment(HorizontalAlignment.LEFT);
+        exampleStyle.setFont(defaultFont);
+        List<Field> fields = Arrays.asList(ExaminationImportDto.class.getDeclaredFields());
+        for (int i = 0; i < examinationImportDtoList.size(); i++) {
+            ExaminationImportDto examinationImportDto = examinationImportDtoList.get(i);
+            XSSFRow row = sheet.createRow(i + 2);
+            for (int j = 0; j < fieldsDtoList.size(); j++) {
+                XSSFCell cell = row.createCell(j);
+                FieldsDto head = fieldsDtoList.get(j);
+                String code = head.getCode();
+                String value = "";
+
+                if (fields.stream().anyMatch(e -> code.equals(e.getName()))) {
+                    // 如果表头属性在数据中存在(则该属性属于基础字段)
+                    String methodName = "get" + SystemConstant.initCap(code);
+                    Method getMethod = examinationImportDto.getClass().getDeclaredMethod(methodName);
+                    value = String.valueOf(getMethod.invoke(examinationImportDto));
+                } else {
+                    // 有可能是扩展字段或者无法匹配的字段 -> 为扩展字段列赋值
+                    // 扩展字段
+                    List<FieldsDto> extendsField = examinationImportDto.getSecondaryFieldList();
+                    for (FieldsDto fieldsDto : extendsField) {
+                        if (code.equals(fieldsDto.getCode())) {
+                            value = fieldsDto.getValue();
+                        }
+                    }
+                }
+                cell.setCellValue(value);
+                cell.setCellStyle(exampleStyle);
+            }
+            // 补充错误信息
+            String errorMessage = examinationImportDto.getErrorMessage();
+            if (SystemConstant.strNotNull(errorMessage)){
+                XSSFCell cell = row.createCell(cellCount - 1);
+                cell.setCellValue(errorMessage);
+                cell.setCellStyle(exampleStyle);
+            }
+        }
+        for (int i = 0; i < cellCount; i++) {
+            sheet.autoSizeColumn(i);
+            sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 10);
+        }
+
+        File excelFileTemp = null;
+        try {
+            FileStoreUtil fileStoreUtil = SpringContextHolder.getBean(FileStoreUtil.class);
+            DictionaryConfig dictionaryConfig = SpringContextHolder.getBean(DictionaryConfig.class);
+            boolean oss = dictionaryConfig.sysDomain().isOss();
+            String ossStr = null;
+            StringJoiner stringJoiner = new StringJoiner("");
+            if (!oss && Objects.nonNull(dictionaryConfig.fssPublicDomain()) && !StringUtils.isBlank(dictionaryConfig.fssPublicDomain().getConfig()) && !dictionaryConfig.fssPublicDomain().getConfig().startsWith(SystemConstant.START_PARENT)) {
+                stringJoiner.add(dictionaryConfig.fssPublicDomain().getConfig()).add(File.separator);
+            }
+            SystemConstant.getDirName(stringJoiner, UploadFileEnum.FILE, true);
+            stringJoiner.add(SystemConstant.getNanoId()).add(SystemConstant.EXCEL_PREFIX);
+
+            String txtDirName = stringJoiner.toString();
+            excelFileTemp = SystemConstant.getFileTempVar(SystemConstant.EXCEL_PREFIX);
+            FileOutputStream outputStream = new FileOutputStream(excelFileTemp);
+            wb.write(outputStream);
+            outputStream.flush();
+            outputStream.close();
+            log.debug("导出Excel结束...");
+
+            String txtFileMd5 = DigestUtils.md5Hex(new FileInputStream(excelFileTemp));
+            if (oss || dictionaryConfig.fssPrivateDomain().getConfig().startsWith(SystemConstant.START_PARENT)) {
+                ossStr = oss ? SystemConstant.OSS : SystemConstant.LOCAL;
+                fileStoreUtil.ossUpload(txtDirName, excelFileTemp, txtFileMd5, fileStoreUtil.getUploadEnumByPath(txtDirName).getFssType());
+            } else {
+                ossStr = SystemConstant.LOCAL;
+                fileStoreUtil.localUpload(txtDirName, new FileInputStream(excelFileTemp), txtFileMd5, LocalCatalogEnum.LOCAL_FILE);
+            }
+            JSONObject json = new JSONObject();
+            json.put(SystemConstant.PATH, stringJoiner.toString());
+            json.put(SystemConstant.TYPE, ossStr);
+            json.put(SystemConstant.UPLOAD_TYPE, UploadFileEnum.FILE);
+            tbTask.setErrorFilePath(json.toJSONString());
+        } catch (Exception 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();
+            tbTask.setSummary(summary);
+            tbTask.setResult(TaskResultEnum.ERROR);
+            if (e instanceof ApiException) {
+                ResultUtil.error((ApiException) e, e.getMessage());
+            } else {
+                ResultUtil.error(e.getMessage());
+            }
+        } finally {
+            if (Objects.nonNull(excelFileTemp)) {
+                excelFileTemp.delete();
+            }
+            TBTaskService tbTaskService = SpringContextHolder.getBean(TBTaskService.class);
+            tbTask.setStatus(TaskStatusEnum.FINISH);
+            tbTaskService.updateById(tbTask);
+        }
+    }
 }

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

@@ -689,10 +689,10 @@ public class TaskLogicServiceImpl implements TaskLogicService {
         }
         // 获取sheet行数
         int totalRows = sheet.getPhysicalNumberOfRows();
-        // 获取sheet列数
+        // 获取sheet列数(列数从第二行开始记录,因为第一行是说明内容)
         int totalCells = 0;
-        if (totalRows > 1 && sheet.getRow(0) != null) {
-            totalCells = sheet.getRow(0).getPhysicalNumberOfCells();
+        if (totalRows > 2 && sheet.getRow(1) != null) {
+            totalCells = sheet.getRow(1).getPhysicalNumberOfCells();
         }
         // 获取sheet的title(第二行为表头)
         Row head = sheet.getRow(1);
@@ -702,8 +702,9 @@ public class TaskLogicServiceImpl implements TaskLogicService {
             String cellValue = String.valueOf(ExcelUtil.convert(head.getCell(i)));
             for (FieldsDto fieldsDto : fieldsDtoList) {
                 if (cellValue.equals(fieldsDto.getName())) {
-                    // 如果通用规则必填字段和excel表头匹配上了,则为该必选字段设置其在excel中的索引
+                    // 如果通用规则必填字段和excel表头匹配上了,则为该必选字段设置其在excel中的索引,并跳出循环体
                     fieldsDto.setIndex(i);
+                    break;
                 }
             }
             headList.add(cellValue);
@@ -720,8 +721,8 @@ public class TaskLogicServiceImpl implements TaskLogicService {
 
         List<ExaminationImportDto> examinationImportDtoList = new ArrayList<>();
 
-        // 错误信息字符串
-        StringJoiner errorString = new StringJoiner(";\r\n");
+        // excel中的数据错误
+        StringJoiner errorRowDate = new StringJoiner(";\r\n");
         // 从第三行开始为数据(第一行说明,第二行表头,第三行往后为数据)
         for (int r = 2; r < totalRows; r++) {
             Row row = sheet.getRow(r);
@@ -745,12 +746,12 @@ public class TaskLogicServiceImpl implements TaskLogicService {
                 if (Objects.nonNull(row)) {
                     Cell cell = row.getCell(index);
                     if (cell == null) {
-                        errorString.add("第" + (r + 1) + "行,第" + (index + 1) + "列,字段[" + name + "]必填");
+                        errorRowDate.add("第" + (r + 1) + "行,第" + (index + 1) + "列,字段[" + name + "]必填");
                         continue;
                     }
                     String cellValue = String.valueOf(ExcelUtil.convert(cell));
                     if (cellValue == null || cellValue.length() < 1 || cellValue.equals("null")) {
-                        errorString.add("第" + (r + 1) + "行,第" + (index + 1) + "列,字段[" + name + "]必填");
+                        errorRowDate.add("第" + (r + 1) + "行,第" + (index + 1) + "列,字段[" + name + "]必填");
                         continue;
                     }
 
@@ -785,17 +786,34 @@ public class TaskLogicServiceImpl implements TaskLogicService {
                 }
             }
             // 解析时间
-            Map<String, Object> timeMap = ConvertUtil.analyzeStartAndEndTime(examinationImportDto.getExamDate(), examinationImportDto.getExamTime());
-            String examStartTime = String.valueOf(timeMap.get("startTime"));
-            String examEndTime = String.valueOf(timeMap.get("endTime"));
-
-            examinationImportDto.setSecondaryFieldList(secondaryFieldList);
-            examinationImportDto.setExamStartTime(examStartTime);
-            examinationImportDto.setExamEndTime(examEndTime);
-            examinationImportDto.setSchoolId(schoolId);
-            examinationImportDtoList.add(examinationImportDto);
+            try {
+                Map<String, Object> timeMap = ConvertUtil.analyzeStartAndEndTime(examinationImportDto.getExamDate(), examinationImportDto.getExamTime());
+                String examStartTime = String.valueOf(timeMap.get("startTime"));
+                String examEndTime = String.valueOf(timeMap.get("endTime"));
+
+                examinationImportDto.setSecondaryFieldList(secondaryFieldList);
+                examinationImportDto.setExamStartTime(examStartTime);
+                examinationImportDto.setExamEndTime(examEndTime);
+                examinationImportDto.setSchoolId(schoolId);
+                examinationImportDtoList.add(examinationImportDto);
+            } catch (Exception e) {
+                errorRowDate.add(e.getMessage());
+            }
+            examinationImportDto.setErrorMessage(errorRowDate.toString());
+        }
+        List<String> errorList = examinationImportDtoList.stream()
+                .map(ExaminationImportDto::getErrorMessage)
+                .filter(SystemConstant::strNotNull)
+                .collect(Collectors.toList());
+        if (!CollectionUtils.isEmpty(errorList)){
+            // 优先处理excel每个独立行的数据异常
+            map.put(SystemConstant.ERROR_DATA_LIST,examinationImportDtoList);
+            map.put("fieldsDtoList",fieldsDtoList);
+            return map;
         }
 
+        // 整体错误信息字符串
+        StringJoiner errorString = new StringJoiner(";\r\n");
         // 课程代码map(同一课程代码,有不同课程名称)
         Map<String, Set<String>> courseCodeMap = examinationImportDtoList.stream().collect(Collectors.groupingBy(ExaminationImportDto::getCourseCode, Collectors.mapping(ExaminationImportDto::getCourseName, Collectors.toSet())));
         for (Map.Entry<String, Set<String>> entry : courseCodeMap.entrySet()) {

+ 28 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/contant/SystemConstant.java

@@ -419,6 +419,19 @@ public class SystemConstant {
      */
     public static final String XLSX = "xlsx";
     public static final String XLS = "xls";
+    public static final String EXAMINATION_DESCRIBE = "说明\n" +
+            "1、【学号】必填;\n" +
+            "2、【座位号】必填;\n" +
+            "3、【姓名】必填;\n" +
+            "4、【课程代码】必填,且与命题任务中的课程代码相互对应;\n" +
+            "5、【课程名称】必填,且与命题任务中的课程名称相互对应;\n" +
+            "6、【试卷编号】必填,且与命题任务中的试卷编号相互对应;\n" +
+            "7、【考点】必填;\n" +
+            "8、【考场】必填;\n" +
+            "9、【考试日期】必填,且格式为YYYY-MM-DD。例如2023-07-09;\n" +
+            "10、【考试时间】必填,且格式为HH:mm-HH:mm。例如18:30-20:30;\n" +
+            "11、请不要删除此行,也不要删除模板中的任何列。\n" +
+            "12、使用前请先删除样例数据。\n";
 
     /**
      * 线程池配置
@@ -1145,6 +1158,21 @@ public class SystemConstant {
         return file;
     }
 
+    /**
+     * 实现首字母大写
+     * @param str str
+     * @return 首字母大写
+     */
+    public static String initCap(String str){
+        if (str == null || "".equals(str)){
+            return str;
+        }else if (str.length() == 1){
+            return str.toUpperCase();
+        }else {
+            return str.substring(0,1).toUpperCase() + str.substring(1);
+        }
+    }
+
 //    /**
 //     * 获取版本号
 //     *