async-excel是easy-excel的辅助组件,抽取通用异步逻辑,通过引入一个starter配置个数据源就可以让导入导出变成异步,无需额外的任何代码,不改变easy-excel的任何特性。

为了支持业务上日益变态的需求,对async-excel进行了一轮重构
当前版本1.1.0。修改了部分写法,兼容1.0.0版本。一些方法被标注为过时。将会在以后的某个版本中移除。

1.1.0版本重构主要内容如下

  • 导出的sheet的声明从框架内部移动到了handler中由开发者自己声明,让easy-excel的功能能够灵活化。
  • 添加了回调的支持,开发者可能会根据整个导入结果需要做一些回调处理
  • 导入的动态表头支持

gitee地址

github地址

demo地址

如果项目对你有帮助可以给个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;
}