异步处理百万数据导出

这是一个项目(guijiu-oms)案例直接拷过来的

  我会把Controller + service + ExcelUtils工具类贴出来

前置准备

  1.异步任务运行表

  2.poi依赖

  3.ftp服务器,相应的上传下载工具类

Controller

    /**
     * 订单列表导出*/
    @ApiOperation("导出")
    @PostMapping("/export")
    public Result export(@RequestBody QueryArgs args, HttpServletRequest req, HttpServletResponse res) throws Exception {
        Long taskId = SEQUtil.getUUID();
        SoExportTaskSchedulePO po = new SoExportTaskSchedulePO();
        po.setId(taskId);
        po.setTaskActionType(SoConstant.TASK_ACTION_TYPE_EXPORT);
        po.setTaskType(SoConstant.TASK_TYPE_SO);
        po.setTaskStatus(SoConstant.TASK_STATUS_NEW);
        UserInfo user = SessionHelper.getUser();
        po.setUserId(user.getUserId());
        po.setUserName(user.getUsername());
        po.setFilterRecord(JSON.toJSONString(args));
        po.setCreateUserid(user.getUserId());
        po.setCreateUsername(user.getUsername());
        po.setCreateTime(new Date());
        po.setCompanyId(SessionHelper.getCompanyId());
        po.setStartTime(new Date());
        Long addWithTx = soExportTaskScheduleService.addWithTx(po);
        SessionCopy sessionCopy = SessionHelper.createSessionCopy();
        soAsyncService.exportSync(args, addWithTx, 0, sessionCopy);
        return ObjectResult.ok(taskId);
    }

service 

  /**
     * 订单导出(订单以及订单行)
     *
     * @param args
     * @param taskId
     */
    @Async("orderTaskExecutor")
    public void exportSync(QueryArgs args, Long taskId, Integer flag, SessionCopy sessionCopy) throws Exception {
        try {
            SessionHelper.copySession(sessionCopy);

        // 任务状态
        //public final static Integer TASK_STATUS_NEW = 1;
        //public final static Integer TASK_STATUS_RUNNING = 2;
        //public final static Integer TASK_STATUS_FAILURE = 3;
        //public final static Integer TASK_STATUS_FINISH = 4;
        //public final static Integer TASK_STATUS_PART = 6;

            Integer taskStatus = SoConstant.TASK_STATUS_RUNNING;//任务状态
            soExportTaskScheduleService.updateFieldsByParamWithTx(new UF("taskStatus", taskStatus).eq("id", taskId));

            List<? extends SoVO> listVO;
            ExcelExportConfig cfg = new ExcelExportConfig();
            cfg.setAutoSizeColumn(false);
            String fileName = "";

            if (flag == 0) {
                listVO = service.exportListSoByPage(args);
                mappingExportFields(cfg, SessionHelper.getPlatformId());
                fileName = "订单列表.xlsx";
            } else {
                listVO = service.getSoOrderLineDetail(args);
                mappingExportOrderLineFields(cfg);
                fileName = "订单行列表.xlsx";
            }

            // 返回url
            String uploadFile = null;
            try {
                // 返回url
                uploadFile = uploadService.uploadFile(fileName, ExcelUtils.getDoExportInputStream(fileName, listVO, cfg));
                taskStatus = SoConstant.TASK_STATUS_FINISH;
            } catch (Exception ex) {
                logger.info("上传文件失败!", ex);
                taskStatus = SoConstant.TASK_STATUS_FAILURE;
            }
            UpdateFieldParam uf = new UF("task_status", taskStatus, "file_path", uploadFile)
                    .update("finish_time", new Date())
                    .update("totalCount",listVO.size());
            if(SoConstant.TASK_STATUS_FINISH.equals(taskStatus)){
                uf.update("successCount",listVO.size());
                uf.update("failedCount",0);
            }else if(SoConstant.TASK_STATUS_FINISH.equals(taskStatus)){
                uf.update("successCount",0);
                uf.update("failedCount",listVO.size());
                uf.update("failedReason","保存文件失败");
            }

            soExportTaskScheduleService.updateFieldsByParamWithTx(uf.eq("id", taskId));
        } finally {
            SessionHelper.clearSessionCopy();
        }
    }

ExcelUtils工具类

public class ExcelUtils {
    
    private ExcelUtils() {}
    
    public static int getMaxExportMaxRows() {
        return OmsEnv.getInstance().getMaxExportRows();
    }
    
    public static int getMaxImportRows() {
        return OmsEnv.getInstance().getMaxImportRows();
    }
    
    public static void doExport(String name, List<?> list, ExcelExportConfig cfg, HttpServletRequest req,
                            HttpServletResponse res) throws Exception {
        ExcelExporter exporter = Excels.newExcelExporterForBigData(getMaxExportMaxRows());

        OutputStream os = res.getOutputStream();
        try {
            WebUtils.setXlsxAttachmentHeader(req, res, name + ".xlsx");

            exporter.setSheetData(list, cfg).export(os);
        } finally {
            Closer.close(os);
        }
    }

    /**
     * @param <T>
     *            要导出的POJO类型,在其字段上使用@{@link ExcelField}注解
     * @param cfg
     *            可以为null表示用默认配置,也可以传用户自定义的部分(仍然会自动增加列的映射关系)
     * @param nameConvertor
     *            nameConvertor @{@link ExcelField}注解中列名转换,常用于多语言,null为不转换
     */
    public static <T> void doExport(String name, List<? extends T> list, Class<T> klass, ExcelExportConfig cfg,
                                ColumnNameConvertor nameConvertor, HttpServletRequest req, HttpServletResponse res) throws Exception {
        try (OutputStream os = res.getOutputStream()) {
            WebUtils.setXlsxAttachmentHeader(req, res, name + ".xlsx");
            ExcelUtil.doExport(list, klass, os, cfg, nameConvertor);
        }
    }

    /**
     * @param <T>
     *            要导入的POJO类型,在其字段上使用@{@link ExcelField}注解
     * @param cfg
     *            可以为null表示用默认配置,也可以传用户自定义的部分(仍然会自动增加列的映射关系)
     * @param nameConvertor
     *            nameConvertor @{@link ExcelField}注解中列名转换,常用于多语言,null为不转换
     */
    public static <T> List<T> doImport(Class<T> resultClass, ExcelParseConfig cfg, ColumnNameConvertor nameConvertor,
                                   MultipartHttpServletRequest request) throws Exception {
        MultipartFile multipartFile = request.getFile("fileData");

        try (InputStream stream = multipartFile.getInputStream()) {
            return doImport(stream, multipartFile.getOriginalFilename(), resultClass, cfg, nameConvertor);
        }
    }
    
    public static <T> List<T> doImport(InputStream stream, String filename, Class<T> resultClass, 
        ExcelParseConfig cfg, ColumnNameConvertor nameConvertor, Integer maxRow)
            throws Exception {
        ExcelParser parser = Excels.newExcelParser(stream, ExcelFileType.getFileType(filename));

        int dataRows = getDataRows(parser, cfg);
        if (dataRows > maxRow) {
            throw new I18nException(CodeEnum.IMPORT_MAX_ROW);
        }
        
        return doImport(parser, resultClass, cfg, nameConvertor);
    }

    public static <T> List<T> doImport(InputStream stream, String filename, Class<T> resultClass, ExcelParseConfig cfg, ColumnNameConvertor nameConvertor)
            throws Exception {
        ExcelParser parser = Excels.newExcelParser(stream, ExcelFileType.getFileType(filename));

        int dataRows = getDataRows(parser, cfg);
        if (dataRows > getMaxImportRows()) {
            throw new I18nException(CodeEnum.IMPORT_MAX_ROW);
        }
        
        return doImport(parser, resultClass, cfg, nameConvertor);
    }
    
    @SuppressWarnings("unchecked")
    public static <T> List<T> doImport(ExcelParser parser, Class<T> resultClass, ExcelParseConfig cfg, ColumnNameConvertor nameConvertor)
            throws Exception {
        try {
            if (! cfg.isAutoMapColName() && cfg.getColNameMap().isEmpty()) {
                cfg = ExcelUtil.makeImportConfig(resultClass, cfg, nameConvertor);
                cfg.setSuppressTypeCastError(true);
            }
            
            if (resultClass.isArray()) {
                return (List<T>) parser.parse2Array(cfg);
            } else if (Map.class.isAssignableFrom(resultClass)) {
                return (List<T>) parser.parse2Map(cfg);
            }
            return (List<T>) parser.parse2Bean(cfg, resultClass);
        } catch (ExcelParseException epe) {
            String errmsg = "格式错误,请检查。";
            Throwable cause = ExceptionUtils.getOriginal(epe);
            if (cause != null && cause instanceof VisibleException) {
                errmsg = ((VisibleException) cause).getMessage();
            }
            String columnTitle = StringUtils.isEmpty(epe.getColName()) ? String.format("第 %d", epe.getColumn() + 1)
                    : cfg.getColNameMap().entrySet().stream()
                    .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey, (a, b) -> b))
                    .get(epe.getColName());
            throw new VisibleException(String.format("第 %d 行,%s列%s", epe.getRow() + 1, columnTitle, errmsg), epe);
        }
    }
    
    public static int getDataRows(ExcelParser parser, ExcelParseConfig cfg) {
        int dataRows = 0;
        for (Integer index : cfg.getSheetIndexes()) {
            dataRows += parser.getWorkbook().getSheetAt(index).getPhysicalNumberOfRows() - cfg.getDataStartRow();
        }
        return dataRows;
    }
    
    public static InputStream getDoExportInputStream(String name, List<?> list, ExcelExportConfig cfg) throws Exception  {
        // 大部分拷贝了doExport的内容
        // 方法是获取了workBook对象,将其输入到字节流中,再转为inputstream
        ExcelExporter exporter = Excels.newExcelExporterForBigData(getMaxExportMaxRows());
        
        //WebUtils.setXlsxAttachmentHeader(req, res, name + ".xlsx");
    
        exporter.setSheetData(list, cfg);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        Workbook workbook = exporter.getWorkbook();
        InputStream fs = null;
        try {
            workbook.write(os);
            byte[] byteArray = os.toByteArray();
            fs = new ByteArrayInputStream(byteArray);
        }finally {
            os.close();
        }
        
        return fs;
    }
    
    /**
     * 将Excel中每单元格的错误以逗号分隔,红色加粗字体写到每行的最后一列
     * @param parseConfig
     * @param excelErrColl
     * @param errColHeader
     * @param os
     * @throws IOException
     */
    public static void writeErrorExcel(ExcelParseConfig parseConfig, ExcelErrorCollector excelErrColl, String errColHeader, OutputStream os)
    throws IOException {
        if (! excelErrColl.hasAnyError()) {
            return;
        }

        Set<String> sheetNameSet = new HashSet<String>();
        
        CellStyle style = excelErrColl.getWorkbook().createCellStyle();
        Font font = excelErrColl.getWorkbook().createFont();
        font.setColor(HSSFFont.COLOR_RED);
        font.setBold(true);
        style.setFont(font);
        int titleRow = parseConfig.getTitleRow();
        
        for (ExcelRowError error : excelErrColl.getRowIterator()) {
            Row row = error.getRow();
            
            if (! sheetNameSet.contains(row.getSheet().getSheetName())) {
                sheetNameSet.add(row.getSheet().getSheetName());
                
                Row firstRow = row.getSheet().getRow(titleRow);
                int col = firstRow.getLastCellNum() + 1;
                row.getSheet().autoSizeColumn(col);
                Cell cell = firstRow.createCell(col, CellType.STRING);
                cell.setCellValue(errColHeader);
                cell.setCellStyle(style);
            }
            
            int col = row.getLastCellNum() + 1;
            Cell cell = row.createCell(col, CellType.STRING);
            cell.setCellValue(ArrayUtils.join(error.getErrors(), ","));
            cell.setCellStyle(style);
        }
        
        excelErrColl.getWorkbook().write(os);
    }

    public static void writeErrorExcel(Workbook workbook, ExcelParseConfig parseConfig, 
            List<LineMsg> errors, OutputStream os) throws IOException {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setColor(HSSFFont.COLOR_RED);
        font.setBold(true);
        style.setFont(font);
        int titleRow = parseConfig.getTitleRow();
        
        Map<Pair<Integer, Integer>, Cell> cellCache = Maps.newHashMap();
        Sheet sheet = workbook.getSheetAt(0);
        Row firstRow = sheet.getRow(titleRow);
        int col = firstRow.getLastCellNum() + 1;
        sheet.autoSizeColumn(col);
        
        for (LineMsg line : errors) {
            Row row = sheet.getRow(line.getLineNo());
            
            Cell cell = cellCache.get(Pair.of(line.getLineNo(), col));
            if (cell == null) {
                cell = row.createCell(col, CellType.STRING);    
                cell.setCellStyle(style);
                cell.setCellValue(line.getMsg());
                cellCache.put(Pair.of(line.getLineNo(), col), cell);
            } else {
                cell.setCellValue(cell.getStringCellValue() + ";" + line.getMsg());
            }
        }
        
        workbook.write(os);
    }
}

ExcelExportConfig类

public class ExcelExportConfig {
    int sheetIndex;
    String sheetName;
    
    boolean autoMapColName = false;
    
    boolean writeHeader = true;
    int startRow;
    int maxDataRows;
    
    String dateFormat = "yyyy-MM-dd HH:mm:ss";
    
    /** field -> column */
    Map<String, String> colNameMap = new LinkedHashMap<String, String>();
    /** field -> columnIdx */
    Map<String, Integer> colIndexMap = new HashMap<String, Integer>();
    
    String[] ignoreProperties;
    
    ExcelExportSheetCallback sheetCallback;
    ExcelExportRowCallback rowCallback;
    ExcelExportCellCallback cellCallback;

    boolean defaultStringType = true;
    Map<String, Class<?>> colTypeMap = new HashMap<String, Class<?>>();
    Map<String, BiConsumer<CellStyle, Cell>> colStyleMap = new HashMap<>();
    Map<String, Integer> colWidthMap = new HashMap<>();
    
    int sheetRandomAccessWindowSize;
    
    boolean autoSizeColumn = true;
    boolean autoBreak = false;
    /** 列表分隔符 */
    String delimiter = ",";
    
    public ExcelExportConfig setSheetCallback(ExcelExportSheetCallback sheetCallback) {
        this.sheetCallback = sheetCallback;
        return this;
    }

    public ExcelExportConfig setRowCallback(ExcelExportRowCallback rowCallback) {
        this.rowCallback = rowCallback;
        return this;
    }

    public ExcelExportConfig setCellCallback(ExcelExportCellCallback cellCallback) {
        this.cellCallback = cellCallback;
        return this;
    }
    
    public ExcelExportConfig withSheetRandomAccessWindowSize(int sheetRandomAccessWindowSize) {
        this.sheetRandomAccessWindowSize = sheetRandomAccessWindowSize;
        return this;
    }
    /**
     * 设置Cell的类型是否为文本类型(默认为true)
     * @param defaultStringType
     * @return
     */
    public ExcelExportConfig setDefaultStringType(boolean defaultStringType) {
        this.defaultStringType = defaultStringType;
        return this;
    }
    
    public ExcelExportConfig mapColType(String col, Class<?> valueType) {
        colTypeMap.put(col, valueType);
        return this;
    }
    public ExcelExportConfig mapColStyle(String col, BiConsumer<CellStyle, Cell> style) {
        colStyleMap.put(col, style);
        return this;
    }
    public ExcelExportConfig mapColWidth(String col, Integer width) {
        colWidthMap.put(col, width);
        return this;
    }
    /**
     * 设置数据从第几行开始,包括Header(如果有),如果是自定义Header,则需要设置此值
     * @param startRow
     * @return
     */
    public ExcelExportConfig setStartRow(int startRow) {
        this.startRow = startRow;
        return this;
    }
    public ExcelExportConfig setMaxDataRows(int maxDataRows) {
        this.maxDataRows = maxDataRows;
        return this;
    }
    public ExcelExportConfig setSheetIndex(int sheetIndex) {
        this.sheetIndex = sheetIndex;
        return this;
    }
    public ExcelExportConfig setSheetName(String sheetName) {
        this.sheetName = sheetName;
        return this;
    }
    /**
     * 是否写数据头(默认为true)
     * @param writeHeader
     * @return
     */
    public ExcelExportConfig setWriteHeader(boolean writeHeader) {
        this.writeHeader = writeHeader;
        return this;
    }

    public ExcelExportConfig setDateFormat(String dateFormat) {
        this.dateFormat = dateFormat;
        return this;
    }

    public ExcelExportConfig setIgnoreProperties(String... ignoreProperties) {
        this.ignoreProperties = ignoreProperties;
        return this;
    }
    /**
     * 设置自动映射列名(默认为true)
     * @param autoMapColName
     * @return
     */
    public ExcelExportConfig setAutoMapColName(boolean autoMapColName) {
        this.autoMapColName = autoMapColName;
        return this;
    }
    /**
     * 设置是否自动调整列宽(默认为true)
     * @param autoSizeColumn
     * @return
     */
    public ExcelExportConfig setAutoSizeColumn(boolean autoSizeColumn) {
        this.autoSizeColumn = autoSizeColumn;
        return this;
    }
    /**
     * 设置是否自动换行(默认为false)
     * @param autoBreak
     * @return
     */
    public ExcelExportConfig setAutoBreak(boolean autoBreak) {
        this.autoBreak = autoBreak;
        return this;
    }
    /**
     * 设定分隔符(默认为半角逗号)
     * @param delimiter
     * @return
     */
    public ExcelExportConfig setDelimiter(String delimiter){
        this.delimiter = delimiter;
        return this;
    }
    
    /**
     * 映射Excel中列的标题,默认标题为key
     * @param key
     * @param column
     * @return
     */
    public ExcelExportConfig mapColName(String key, String column) {
        colNameMap.put(key, column);
        colIndexMap.put(key, colIndexMap.size());
        return this;
    }
    /**
     * 映射Excel中列的索引,默认按mapColName(..)的添加顺序,如果未调用mapColIndex(..),则可能为乱序
     * @param key
     * @param column
     * @return
     */
    public ExcelExportConfig mapColIndex(String key, int column) {
        if (column < colIndexMap.size()) { 
            // 如果column小于colIndexMap的大小,则一定会与现有的列顺序冲突,需要作移位处理
            String oldKey = null;
            for (String k : colIndexMap.keySet()) {
                if (colIndexMap.get(k) == column) {
                    oldKey = k;
                    break;
                }
            }
            colIndexMap.remove(oldKey);
            colIndexMap.put(oldKey, colIndexMap.size());
        }
        colIndexMap.put(key, column);
        return this;
    }
    
    /*------------------------------------------------------------------------------*/

    public String getSheetName() {
        return sheetName;
    }

    public boolean isWriteHeader() {
        return writeHeader;
    }

    public int getStartRow() {
        return startRow;
    }
    
    public int getMaxDataRows() {
        return maxDataRows;
    }

    public String getDateFormat() {
        return dateFormat;
    }

    public Map<String, String> getColNameMap() {
        return colNameMap;
    }

    public Map<String, Integer> getColIndexMap() {
        return colIndexMap;
    }

    public boolean isAutoMapColName() {
        return autoMapColName;
    }

    public String[] getIgnoreProperties() {
        return ignoreProperties;
    }

    public ExcelExportSheetCallback getSheetCallback() {
        return sheetCallback;
    }

    public ExcelExportRowCallback getRowCallback() {
        return rowCallback;
    }

    public ExcelExportCellCallback getCellCallback() {
        return cellCallback;
    }

    public boolean isDefaultStringType() {
        return defaultStringType;
    }

    public Map<String, Class<?>> getColTypeMap() {
        return colTypeMap;
    }
    public Map<String, BiConsumer<CellStyle, Cell>> getColStyleMap() {
        return colStyleMap;
    }
    public Map<String, Integer> getColWidthMap() {
        return colWidthMap;
    }

    public String getDelimiter() {
        return delimiter;
    }
    
}

 

posted @ 2020-04-30 19:13  九涯  阅读(1642)  评论(2编辑  收藏  举报