async-excel是easy-excel的辅助组件,抽取通用异步逻辑,通过引入一个starter配置个数据源就可以让导入导出变成异步,无需额外的任何代码,不改变easy-excel的任何特性。
为了支持业务上日益变态的需求,对async-excel进行了一轮重构
当前版本1.1.0。修改了部分写法,兼容1.0.0版本。一些方法被标注为过时。将会在以后的某个版本中移除。
1.1.0版本重构主要内容如下
gitee地址
github地址
demo地址
如果项目对你有帮助可以给个star支持下
async-excel基于easy-excel抽取了异步逻辑,并且使用了sping的父子容器,适配了springboot-starter,使用该组件非常简单
引入starter
com.asyncexcel async-excel-springboot-starter 1.1.0
初始化数据库
drop table if exists excel_task;
CREATE TABLE `excel_task` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',`type` tinyint(2) NOT NULL COMMENT '类型:1-导入,2-导出',`status` tinyint(2) NOT NULL DEFAULT 0 COMMENT '状态:0-初始,1-进行中,2-完成,3-失败',`estimate_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '预估总记录数',`total_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '实际总记录数',`success_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '成功记录数',`failed_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '失败记录数',`file_name` varchar(200) DEFAULT NULL COMMENT '文件名',`file_url` varchar(500) DEFAULT NULL COMMENT '文件路径',`failed_file_url` varchar(500) DEFAULT NULL COMMENT '失败文件路径',`failed_message` varchar(255) DEFAULT NULL COMMENT '失败消息',`start_time` datetime DEFAULT NULL COMMENT '开始时间',`end_time` datetime DEFAULT NULL COMMENT '结束时间',`tenant_code` varchar(50) default NULL COMMENT '租户编码',`create_user_code` varchar(50) default NULL COMMENT '用户编码',`business_code` varchar(50) default NULL COMMENT '业务编码',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='导入导出任务';
配置数据源,父子容器多数据源配置,不影响你原有数据源
#aysncexcel 数据源
spring.excel.datasource.url=jdbc:mysql://localhost:3306/async-excel?serverTimezone=GMT%2B8&autoReconnect=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&&useCursorFetch=true&&rewriteBatchedStatements=true
spring.excel.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.excel.datasource.password=root
spring.excel.datasource.username=root
多sheet导出
controller 注入excelService
@AutoWire
ExcelService excelService
//导出最简示例,支持多个sheet导出@PostMapping("/exports")public Long exports(){DataExportParam dataExportParam=new DataExportParam().setExportFileName("用户导出").setLimit(5);return excelService.doExport(dataExportParam,UserExportHandler.class, UserExportHandlerA.class);}
第一个sheet
@ExcelHandle
public class UserExportHandler implements ExportHandler {@AutowiredIUserService userService;@Overridepublic void init(ExcelContext ctx, DataParam param) {ExportContext context = (ExportContext) ctx;//此处的sheetNo会被覆盖,为了兼容一个文件多sheet导出WriteSheet sheet = EasyExcel.writerSheet(0, "第一个sheet").head(UserExportModel.class).build();context.setWriteSheet(sheet);}@Overridepublic ExportPage exportData(int startPage, int limit, DataExportParam dataExportParam) {IPage iPage = new Page<>(startPage, limit);IPage page = userService.page(iPage);List list = ExportListUtil.transform(page.getRecords(), UserExportModel.class);ExportPage result = new ExportPage<>();result.setTotal(page.getTotal());result.setCurrent(page.getCurrent());result.setSize(page.getSize());result.setRecords(list);return result;}@Overridepublic void beforePerPage(ExportContext ctx, DataExportParam param) {//分页执行,每页开始执行前}@Overridepublic void afterPerPage(List list, ExportContext ctx, DataExportParam param) {//分页执行,每页执行完成后}@Overridepublic void callBack(ExcelContext ctx, DataParam param) {//全部执行完成后回调}
}
第二个sheet
@ExcelHandle
public class UserExportHandlerA implements ExportHandler {@AutowiredIUserService userService;@Overridepublic void init(ExcelContext ctx, DataParam param) {ExportContext context = (ExportContext) ctx;//此处的sheetNo会被覆盖,为了兼容一个文件多sheet导出WriteSheet sheet = EasyExcel.writerSheet(0, "第二个sheet").head(UserExportModel.class).build();context.setWriteSheet(sheet);}@Overridepublic ExportPage exportData(int startPage, int limit, DataExportParam dataExportParam) {IPage iPage = new Page<>(startPage, limit);IPage page = userService.page(iPage);List list = ExportListUtil.transform(page.getRecords(), UserExportModel.class);ExportPage result = new ExportPage<>();result.setTotal(page.getTotal());result.setCurrent(page.getCurrent());result.setSize(page.getSize());result.setRecords(list);return result;}@Overridepublic void beforePerPage(ExportContext ctx, DataExportParam param) {//分页执行,每页开始执行前}@Overridepublic void afterPerPage(List list, ExportContext ctx, DataExportParam param) {//分页执行,每页执行完成后}@Overridepublic void callBack(ExcelContext ctx, DataParam param) {//全部执行完成后回调}
}
动态表头导入
表头不确定,需要业务上进行判断怎么办?
只要将你的导入实体继承至ImportRowMap即可
@Data
public class ImportRowMap extends ImportRow {@ExcelIgnoreprivate Map headMap;@ExcelIgnoreprivate Map dataMap;
}
这个类带了两个map 一个是表头的map,一个是数据的map
根据map自己去做对应的数据处理
示例代码
@ExcelHandle
public class DynamicHeadImportsHandler implements ImportHandler {@AutowiredIOplogService service;@Overridepublic List importData(List list, DataImportParam param)throws Exception {List errorMsgList = new ArrayList<>();//List oplogs = ExportListUtil.transform(list, Oplog.class);for (DynamicHeadImportsModel dynamicHeadImportsModel : list) {//处理固定列Oplog oplog = new Oplog();oplog.setOpUser(dynamicHeadImportsModel.getOpUser());oplog.setOpRemark(dynamicHeadImportsModel.getOpRemark());oplog.setOpSummary(dynamicHeadImportsModel.getOpSummary());oplog.setOpContent(dynamicHeadImportsModel.getOpContent());//模拟错误if (dynamicHeadImportsModel.getRow()%2==0){errorMsgList.add(new ErrorMsg(dynamicHeadImportsModel.getRow(),"2的倍数行出错"));}//处理动态列Map headMap = dynamicHeadImportsModel.getHeadMap();Map dataMap = dynamicHeadImportsModel.getDataMap();Map stringCellMap = mergeHeadAndData(headMap, dataMap);System.out.println(stringCellMap.keySet());}System.out.println("分页数据处理完成");return errorMsgList;}private Map mergeHeadAndData(Map headMap,Map dataMap){Set headKeySet = headMap.keySet();Map mergeMap=new LinkedHashMap<>();Iterator iterator = headKeySet.iterator();for (int i = 0; i Integer key= iterator.next();mergeMap.put(headMap.get(key),dataMap.get(key));}return mergeMap;}//新增导入导出完成后的回调支持,如果多sheet导出时,也是整个生命周期完成后按顺序执行@Overridepublic void callBack(ExcelContext ctx, DataParam param) {//整个生命周期完成后执行一次该方法System.out.println("执行完成");}
}
导入入参说明
public class DataImportParam extends DataParam {/*** 输入流*/private InputStream stream;/*** 文件名称*/private String filename;/*** 导入对应的实体类*/private Class> model;/*** 分批次大小,如果你导入1w条数据,每次1000会分10次读到内存中*/private int batchSize = 1000;/*** 是否限制导入行数,默认false,如果限制行数将会触发行数限制异常,例如限制1000行,你的文件如果超过1000行将会抛异常*/private boolean validMaxRows = false;/*** 行数限制validMaxRows=true时起作用*/private int maxRows = 1000;/*** 是否进行表头校验,顺序单元格内容都应该与实体类保持一致。*/private boolean validHead = true;}
导出入参说明
public class DataExportParam extends DataParam {/*** 分页大小*/private int limit=1000;/*** 导出文件名称*/private String exportFileName;/*** 写入excel的sheetName*/@Deprecatedprivate String sheetName;/*** 是否动态表头,默认false。*/@Deprecatedprivate boolean dynamicHead;/*** 当dynamicHead=true时需要传一个动态表头进来*/@Deprecatedprivate List> headList;/*** 表头对应的实体类*/@Deprecatedprivate Class> headClass;/*** 自定义写处理器为了,自定义样式,表格合并之类的easyExcel原生扩展*/@Deprecatedprivate List writeHandlers;/*** 自定义类型转换器easyExcel原生扩展*/@Deprecatedprivate List> converters;
}
@Data
public class DataParam {//业务额外参数private Map parameters;//租户参数private String tenantCode;//用户参数private String createUserCode;//业务参数用于区分文件展示的不同模块private String businessCode;
}