easy-excel通用异步导入导出神辅助

news/2024/7/21 3:45:08 标签: excel

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;
}

http://www.niftyadmin.cn/n/31634.html

相关文章

个人简历(前端)

简历导航个人基本信息为了节约你的时间&#xff0c;请先看这一段我能为公司提供一个什么样的程序员我会的和我不会的个人经历和个人想法工作履历关于我在上一家公司“创业”个人基本信息 标题信息姓名保密性别男学历本科&#xff08;浙工商计算机专业&#xff09;工作经验6年技…

关于原型和原型链的整理学习

关于原型和原型链是很多人学习或面试时遇到的问题&#xff0c;可能部分不懂&#xff0c;部分懂但不会说&#xff0c;下面关于原型和原型链进行简单的整理总结&#xff0c;希望可以帮助到大家。 一、JS中的原型和原型链 1、原型说明 所有的引用类型&#xff08;数组、函数、对…

如何用ChatGPT高效完成工作

如何用ChatGPT高效完成工作 过完年刚开工&#xff0c;很多人还没有从假期综合症中走出来&#xff0c;不想上班&#xff0c;总想摸鱼&#xff0c;可是手上的工作还是要完成的。都2023年了&#xff0c;是时候让ChatGPT来帮我们完成工作了&#xff01;本文将教你如何用ChatGPT高效…

JVM的类加载

什么是类加载&#xff1f;java程序运行前&#xff0c;要经过编译即.java>.class文件。运行的时候java进程(JVM)就会读取对应的.class文件&#xff0c;并解析内容&#xff0c;在内存中构造出类对象并进行初始化&#xff08;类对象就是描述这个类有哪些属性&#xff0c;哪些方…

yolov5 模型输出的格式解析

工作需要&#xff0c; 又需要对yolov5 输出的模型进行转onnx 再用c进行后续处理。 两个问题。 yolov5 的模型输出的是个啥啊&#xff1f;转成onnx后输出的和yolov5输出的处理是否一样呢&#xff1f; 关于第一个问题&#xff0c;yolov5 的模型输出的是个啥啊&#xff1f; 以前…

【数学建模】常用算法-主成分分析PCA的Python实现

1前言 本文主要讲解主成分分析析法&#xff08;PCA&#xff09;的python实现&#xff0c;后续会跟进实例分析 2 原理-代码实现 2.1 实现步骤 主成分分析PCA是一种应用广泛的和降维方法&#xff0c;对其实现做以下归纳 2.2 代码实现 导入包 import numpy as np定义计算协…

RabbitMQ面试题

什么是 MQ MQ(message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是 message 而已。 还是一种跨进程的通信机制&#xff0c;用于上下游传递消息。 在互联网架构中&#xff0c;MQ 是一种非常常见的…

【双向链表】数据结构双向链表的实现

前言&#xff1a; 前一期我们已经学习过单链表了&#xff0c;今天我们来学习链表中的双向链表&#xff01; 目录1.概念以及结构2.双向链表结点结构体3.接口实现3.1动态申请一个结点3.2初始化链表3.3打印链表3.4双向链表尾插3.5 双向链表尾删3.6双向链表头插3.7双向链表头删3.8双…