zoukankan      html  css  js  c++  java
  • 异步处理百万数据导出

    这是一个项目(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;
        }
        
    }
  • 相关阅读:
    不务正业系列-浅谈《过气堡垒》,一个RTS玩家的视角
    [LeetCode] 54. Spiral Matrix
    [LeetCode] 40. Combination Sum II
    138. Copy List with Random Pointer
    310. Minimum Height Trees
    4. Median of Two Sorted Arrays
    153. Find Minimum in Rotated Sorted Array
    33. Search in Rotated Sorted Array
    35. Search Insert Position
    278. First Bad Version
  • 原文地址:https://www.cnblogs.com/jiuya/p/12810796.html
Copyright © 2011-2022 走看看