async-excel是easy-excel的辅助组件,抽取通用异步逻辑,通过引入一个starter配置个数据源就可以让导入导出变成异步,无需额外的任何代码,不改变easy-excel的任何特性。
为了支持业务上日益变态的需求,对async-excel进行了一轮重构
当前版本1.1.0。修改了部分写法,兼容1.0.0版本。一些方法被标注为过时。将会在以后的某个版本中移除。
1.1.0版本重构主要内容如下
- 导出的sheet的声明从框架内部移动到了handler中由开发者自己声明,让easy-excel的功能能够灵活化。
- 添加了回调的支持,开发者可能会根据整个导入结果需要做一些回调处理
- 导入的动态表头支持
如果项目对你有帮助可以给个star支持下
async-excel基于easy-excel抽取了异步逻辑,并且使用了sping的父子容器,适配了springboot-starter,使用该组件非常简单
引入starter
<dependency>
<groupId>com.asyncexcel</groupId>
<artifactId>async-excel-springboot-starter</artifactId>
<version>1.1.0</version>
</dependency>
初始化数据库
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<UserExportModel> {
@Autowired
IUserService userService;
@Override
public 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);
}
@Override
public ExportPage<UserExportModel> exportData(int startPage, int limit, DataExportParam dataExportParam) {
IPage<User> iPage = new Page<>(startPage, limit);
IPage page = userService.page(iPage);
List<UserExportModel> list = ExportListUtil.transform(page.getRecords(), UserExportModel.class);
ExportPage<UserExportModel> result = new ExportPage<>();
result.setTotal(page.getTotal());
result.setCurrent(page.getCurrent());
result.setSize(page.getSize());
result.setRecords(list);
return result;
}
@Override
public void beforePerPage(ExportContext ctx, DataExportParam param) {
//分页执行,每页开始执行前
}
@Override
public void afterPerPage(List<UserExportModel> list, ExportContext ctx, DataExportParam param) {
//分页执行,每页执行完成后
}
@Override
public void callBack(ExcelContext ctx, DataParam param) {
//全部执行完成后回调
}
}
第二个sheet
@ExcelHandle
public class UserExportHandlerA implements ExportHandler<UserExportModel> {
@Autowired
IUserService userService;
@Override
public 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);
}
@Override
public ExportPage<UserExportModel> exportData(int startPage, int limit, DataExportParam dataExportParam) {
IPage<User> iPage = new Page<>(startPage, limit);
IPage page = userService.page(iPage);
List<UserExportModel> list = ExportListUtil.transform(page.getRecords(), UserExportModel.class);
ExportPage<UserExportModel> result = new ExportPage<>();
result.setTotal(page.getTotal());
result.setCurrent(page.getCurrent());
result.setSize(page.getSize());
result.setRecords(list);
return result;
}
@Override
public void beforePerPage(ExportContext ctx, DataExportParam param) {
//分页执行,每页开始执行前
}
@Override
public void afterPerPage(List<UserExportModel> list, ExportContext ctx, DataExportParam param) {
//分页执行,每页执行完成后
}
@Override
public void callBack(ExcelContext ctx, DataParam param) {
//全部执行完成后回调
}
}
动态表头导入
表头不确定,需要业务上进行判断怎么办?
只要将你的导入实体继承至ImportRowMap即可
@Data
public class ImportRowMap extends ImportRow {
@ExcelIgnore
private Map<Integer, String> headMap;
@ExcelIgnore
private Map<Integer, Cell> dataMap;
}
这个类带了两个map 一个是表头的map,一个是数据的map
根据map自己去做对应的数据处理
示例代码
@ExcelHandle
public class DynamicHeadImportsHandler implements ImportHandler<DynamicHeadImportsModel> {
@Autowired
IOplogService service;
@Override
public List<ErrorMsg> importData(List<DynamicHeadImportsModel> list, DataImportParam param)
throws Exception {
List<ErrorMsg> errorMsgList = new ArrayList<>();
//List<Oplog> 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<Integer, String> headMap = dynamicHeadImportsModel.getHeadMap();
Map<Integer, Cell> dataMap = dynamicHeadImportsModel.getDataMap();
Map<String, Cell> stringCellMap = mergeHeadAndData(headMap, dataMap);
System.out.println(stringCellMap.keySet());
}
System.out.println("分页数据处理完成");
return errorMsgList;
}
private Map<String,Cell> mergeHeadAndData(Map<Integer, String> headMap,Map<Integer, Cell> dataMap){
Set<Integer> headKeySet = headMap.keySet();
Map<String,Cell> mergeMap=new LinkedHashMap<>();
Iterator<Integer> iterator = headKeySet.iterator();
for (int i = 0; i <headKeySet.size() ; i++) {
Integer key= iterator.next();
mergeMap.put(headMap.get(key),dataMap.get(key));
}
return mergeMap;
}
//新增导入导出完成后的回调支持,如果多sheet导出时,也是整个生命周期完成后按顺序执行
@Override
public 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<T> extends DataParam {
/**
* 分页大小
*/
private int limit=1000;
/**
* 导出文件名称
*/
private String exportFileName;
/**
* 写入excel的sheetName
*/
@Deprecated
private String sheetName;
/**
* 是否动态表头,默认false。
*/
@Deprecated
private boolean dynamicHead;
/**
* 当dynamicHead=true时需要传一个动态表头进来
*/
@Deprecated
private List<List<String>> headList;
/**
* 表头对应的实体类
*/
@Deprecated
private Class<?> headClass;
/**
* 自定义写处理器为了,自定义样式,表格合并之类的easyExcel原生扩展
*/
@Deprecated
private List<WriteHandler> writeHandlers;
/**
* 自定义类型转换器easyExcel原生扩展
*/
@Deprecated
private List<Converter<?>> converters;
}
@Data
public class DataParam {
//业务额外参数
private Map<String, Object> parameters;
//租户参数
private String tenantCode;
//用户参数
private String createUserCode;
//业务参数用于区分文件展示的不同模块
private String businessCode;
}