• 异步处理百万数据导出


    这是一个项目(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;
        }
        
    }
  • 相关阅读:
    CLR via C# 3rd Edition下载
    SQL中的CASE WHEN语句
    为何要写注释?
    插件购票的公平性分析
    数据查询
    国产操作系统的市场运作策略
    0/0=2?
    为什么0.1无法被二进制小数精确表示?
    .net 可能要遭到取缔
    思考问题要先注意主体
  • 原文地址:https://www.cnblogs.com/jiuya/p/12810796.html
Copyright © 2020-2023  润新知