Excel流式多线程带进度条功能的导入框架

[复制链接]
发表于 2025-9-29 20:35:10 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
形貌

利用Java代码封装的一个Excel批量导入框架,利用EasyExcel完成excel文件方面的交互,重在整合功能,仅需少量代码就可以在本身的项目中快速引入该功能。可以流式分批读取Excel中的数据,克制OOM,利用多线程进步处置惩罚速率,异步线程处置惩罚便于前端查察导入进度与处置惩罚效果。
框架代码采取了操持模式中的战略模式,只必要实现此中的战略类(针对本身的业务来处置惩罚每行数据),就可以快速利用导入功能,而且给出了前端的配套代码示例(vue)。
demo项目所在:https://github.com/destinyol/excel-multi-import-progress
(demo项目不包罗数据库交互部门,以是无法运行,只是演示代码怎样利用)
框架项目文件结构:

ps:修改ExcelImportMainTool 中的 BATCH_COUNT 可自界说每次从excel中读取多少行数据,默认1000行
效果展示


可调参数

ExcelImportMainTool类中有一些可以根据现实情况自行调解的参数
  1. public static final int BATCH_COUNT = 1000; // 批量处理,每批从excel中读取并处理的数据行数,可根据情况自行修改
  2. public int BATCH_INSERT_COUNT = 200; // 使用批量插入时的每批次处理数量(传入handleMultiDataAndSave函数的数组大小),可根据情况自行修改,可动态设置
  3. public static final boolean PROCESS_SAVE_RESULT_LIST = true; // redis保存进度条时是否保存报错结果数组ExcelResDto,true保存,false不保存
复制代码
利用方法

1、继续EasyExcelReadDataAbstract抽象类并实现此中EasyExcelReadData接口的方法,该子类是对应EasyExcel导入的实体类,用来存储单行数据,必要实现此中的方法
  1. public interface EasyExcelReadData {
  2.     /**
  3.      * 去掉每个字段首尾空格(若不需要则方法体为空即可)
  4.      *      (处理过程中每行会调用一次)
  5.      */
  6.     public void trimAllFields();
  7.     /**
  8.      * 为了排除表格空行(建议实现该方法,空行比较容易出现)
  9.      *      检查该行中的每个属性是否都为null或空字符串,如果是,则返回true;否则返回false
  10.      *      (处理过程中每行会调用一次)
  11.      */
  12.     public boolean dataIsAllEmpty();
  13. }
复制代码
比方:
  1. @Data
  2. public class CustomerImportDto extends EasyExcelReadDataAbstract {
  3.     @ExcelProperty(value = "客户名称")
  4.     private String name;
  5.     @ExcelProperty(value = "手机号")
  6.     private String phone;
  7.         // ***********************
  8.     // 省略各种字段
  9.    
  10.    
  11.     @Override
  12.     public boolean dataIsAllEmpty(){
  13.         return (name == null || name.isEmpty()) &&
  14.                 (phone == null || phone.isEmpty());
  15.     }
  16.     @Override
  17.     public void trimAllFields() {
  18.         if (name != null) name = name.trim();
  19.         if (phone != null) phone = phone.trim();
  20.     }
  21. }
复制代码
2、实现DataDealHandler接口及对应方法,该实现类是用于自界说的数据处置惩罚与生存,便于衔接差别的业务场景
(要用批量插入就实现handleMultiDataAndSave方法,要用逐条插入就实现handleOneDataAndSave)
  1. public interface DataDealHandler {
  2.     /**
  3.      * 返回模板表头行数(根据对应模板配置实现该方法,例如表头三行:return 3;)
  4.      * @return
  5.      */
  6.     public Integer getHeadRows();
  7.     /**
  8.      * 处理单条数据(比如去重,特殊判断,或者其他自定义业务)
  9.      *  并自行保存该条数据到数据库
  10.      * @param importData EasyExcelReadDataAbstract的子类
  11.      * @return 返回结果内容(显示给前端,表格行号+错误内容)。若返回类为null,则该条数据导入成功,不用显示;若不为null,则将其放入错误结果集中
  12.      */
  13.     @Transactional(rollbackFor = Exception.class)
  14.     default public ExcelResDto handleOneDataAndSave(EasyExcelReadData importData){
  15.         throw new RuntimeException("未实现逐条插入数据处理函数handleOneDataAndSave");
  16.     }
  17.     /**
  18.      * 批量处理数据(比如去重,特殊判断,或者其他自定义业务)
  19.      *  并自行保存数据到数据库,若抛出错误则这一批全部回滚
  20.      * @param importDataList EasyExcelReadDataAbstract的子类list
  21.      * @return 返回结果内容数组(显示给前端,表格行号+错误内容)。若数组长度为0,则全部成功;如果长度大于0,则将其放入错误结果集中
  22.      */
  23.     @Transactional(rollbackFor = Exception.class)
  24.     default public List<ExcelResDto> handleMultiDataAndSave(List<EasyExcelReadData> importDataList){
  25.         throw new RuntimeException("未实现批量插入数据处理函数handleMultiDataAndSave");
  26.     }
  27. }
复制代码
3、直接调用
  1. // --------------------------------------导入任务启动的地方---------------------------------
  2. // 构造DataDealHandler的实现类的实例对象
  3. DataDealHandlerImpl handler = new DataDealHandlerImpl(xxxxx,xxxxx自定义构造);
  4. // 启动导入主函数并异步执行,返回redis中保存的该进度的processKey,可以直接返回给前端用于查询进度
  5. // (逐条插入,需实现对应方法)
  6. String processKey = ExcelImportMainTool.buildImport(
  7.     EasyExcelReadDataAbstract的子类.class,
  8.     handler,
  9.     redisTemplate        // 进度保存在redis中,所以需要自行注入redisTemplate
  10. ).runAsyncSingle(multipartFile);
  11. // (批量插入,需实现对应方法)
  12. String processKey = ExcelImportMainTool.buildImport(
  13.     EasyExcelReadDataAbstract的子类.class,
  14.     handler,
  15.     redisTemplate        // 进度保存在redis中,所以需要自行注入redisTemplate
  16. ).setBatchInsertCount(250) // 设置批量插入每批传入`DataDealHandler`中的数据量,不设置则默认200行
  17. .runAsyncMulti(multipartFile);
复制代码
  1. // ---------------------------------------轮询或其他手段,获取任务进度-----------------------
  2. // 获取进度(静态方法,需传入redisTemplate)
  3. ImportProgress progress = ExcelImportMainTool.getProgress(redisTemplate, processKey);
复制代码
至此整个功能就跑起来了
利用示例

数据实体类实现类
  1. /**
  2. * 示例:easyExcel导入的数据实体类
  3. *      ( EasyExcelReadDataAbstract的子类 )
  4. */
  5. @Data
  6. public class CustomerImportDto extends EasyExcelReadDataAbstract {
  7.     @ExcelProperty(value = "销售")
  8.     private String saleUserName;
  9.     @ExcelProperty(value = "客户名称")
  10.     private String name;
  11.     @ExcelProperty(value = "联系人")
  12.     private String contacts;
  13.     @ExcelProperty(value = "联系电话")
  14.     private String phone;
  15.     @ExcelProperty(value = "统一社会信用代码")
  16.     private String sucCode;
  17.     @ExcelProperty(value = "法人")
  18.     private String legalPerson;
  19.     @ExcelProperty(value = "注册资本(单位万元)")
  20.     private String registerMoney;
  21.     @ExcelProperty(value = "客户地址")
  22.     private String address;
  23.     /**
  24.      * 检查类中的每个属性是否为null或空字符串,如果是,则返回true;否则返回false
  25.      * @return
  26.      */
  27.     @Override
  28.     public boolean dataIsAllEmpty(){
  29.         return (saleUserName == null || saleUserName.isEmpty()) &&
  30.                 (name == null || name.isEmpty()) &&
  31.                 (contacts == null || contacts.isEmpty()) &&
  32.                 (phone == null || phone.isEmpty()) &&
  33.                 (sucCode == null || sucCode.isEmpty()) &&
  34.                 (legalPerson == null || legalPerson.isEmpty()) &&
  35.                 (registerMoney == null || registerMoney.isEmpty()) &&
  36.                 (address == null || address.isEmpty());
  37.     }
  38.     @Override
  39.     public void trimAllFields() {
  40.         if (saleUserName != null){
  41.             saleUserName = saleUserName.replaceAll("\\s+", "");
  42.         }
  43.         if (name != null) name = name.trim();
  44.         if (contacts != null) contacts = contacts.trim();
  45.         if (phone != null) phone = phone.trim();
  46.         if (sucCode != null) sucCode = sucCode.trim();
  47.         if (legalPerson != null) legalPerson = legalPerson.trim();
  48.         if (registerMoney != null) registerMoney = registerMoney.trim();
  49.         if (address != null) address = address.trim();
  50.     }
  51. }
复制代码
战略类实现类
  1. /**
  2. * 示例:客户批量导入handler
  3. *      ( DataDealHandler实现类 )
  4. */
  5. public class CustomerImportDataHandler implements DataDealHandler {
  6.     private UserMapper userMapper;
  7.     private TdCustomerMapper tdCustomerMapper;
  8.     public CustomerImportDataHandler(UserMapper userMapper, TdCustomerMapper tdCustomerMapper) {
  9.         this.userMapper = userMapper;
  10.         this.tdCustomerMapper = tdCustomerMapper;
  11.     }
  12.     @Override
  13.     public Integer getHeadRows() {
  14.         return 2;
  15.     }
  16.     @Override
  17.     @Transactional(rollbackFor = Exception.class)
  18.     public ExcelResDto handleOneDataAndSave(EasyExcelReadData importData) {
  19.         CustomerImportDto customerImportDto = (CustomerImportDto) importData;
  20.         ExcelResDto resDto = null;
  21.         TdUser user = userMapper.queryOneByUserName(customerImportDto.getSaleUserName());
  22.         if (user == null){
  23.             resDto = ExcelResDto.build(customerImportDto,"该姓名用户不存在:("+customerImportDto.getSaleUserName()+") ,对应客户名:"+customerImportDto.getName());
  24.             return resDto;
  25.         }
  26.         TdCustomer step = new TdCustomer();
  27.         step.setName(customerImportDto.getName());
  28.         step.setContacts(customerImportDto.getContacts());
  29.         step.setAddress(customerImportDto.getAddress());
  30.         step.setPhone(customerImportDto.getPhone());
  31.         step.setCreateBy(user.getUserId());
  32.         step.setLegalPerson(customerImportDto.getLegalPerson());
  33.         step.setSucCode(customerImportDto.getSucCode());
  34.         step.setRegisterMoney(customerImportDto.getRegisterMoney());
  35.         if (isBlank(step.getName())){
  36.             resDto = ExcelResDto.build(customerImportDto,"客户名不能为空");
  37.             return resDto;
  38.         }
  39.         TdCustomer customerByName = tdCustomerMapper.getCustomerByName(customerImportDto.getName());
  40.         if (customerByName != null) {
  41.             resDto = ExcelResDto.build(customerImportDto,"客户名重复:"+customerImportDto.getName());
  42.             return resDto;
  43.         }
  44.         if (isBlank(step.getContacts())){
  45.             resDto = ExcelResDto.build(customerImportDto,"客户联系人不能为空,客户名:"+customerImportDto.getName());
  46.             return resDto;
  47.         }
  48.         if (isBlank(step.getPhone())){
  49.             resDto = ExcelResDto.build(customerImportDto,"客户联系方式不能为空,客户名:"+customerImportDto.getName());
  50.             return resDto;
  51.         }
  52.         boolean save = tdCustomerMapper.insert(step) != 0;
  53.         if (!save){
  54.             resDto = ExcelResDto.build(customerImportDto,"客户添加失败,客户名:"+customerImportDto.getName());
  55.         }
  56.         return resDto;
  57.     }
  58.     @Override
  59.     @Transactional(rollbackFor = Exception.class)
  60.     public List<ExcelResDto> handleMultiDataAndSave(List<EasyExcelReadData> importDataList) {
  61.         // 结果数组
  62.         List<ExcelResDto> res = new ArrayList<>();
  63.         List<CustomerImportDto> customerImportDtoList = new ArrayList<>();
  64.         for (EasyExcelReadData easyExcelReadData : importDataList) {
  65.             customerImportDtoList.add((CustomerImportDto)easyExcelReadData);
  66.         }
  67.         List<TdCustomer> tdCustomerList = new ArrayList<>();
  68.         for (CustomerImportDto customerImportDto : customerImportDtoList) {
  69.             TdCustomer step = new TdCustomer();
  70.             step.setName(customerImportDto.getName());
  71.             step.setContacts(customerImportDto.getContacts());
  72.             step.setAddress(customerImportDto.getAddress());
  73.             step.setPhone(customerImportDto.getPhone());
  74.             step.setLegalPerson(customerImportDto.getLegalPerson());
  75.             step.setSucCode(customerImportDto.getSucCode());
  76.             step.setRegisterMoney(customerImportDto.getRegisterMoney());
  77.         }
  78.         //==========================================================
  79.         // 省略各种业务检查,判断重复之类的
  80.         //==========================================================
  81.         Boolean insetCheck = tdCustomerMapper.saveBatch(tdCustomerList);
  82.         if (!insetCheck){
  83.             for (CustomerImportDto customerImportDto : customerImportDtoList) {
  84.                 res.add(ExcelResDto.build(customerImportDto,customerImportDto.getName()+"添加失败"));
  85.             }
  86.         }
  87.         return res;
  88.     }
  89. }
复制代码
接口示例
  1.         /**
  2.      * excel 导入客户
  3.      * @return
  4.      */
  5.     @PostMapping("/importExcel")
  6.     public Response importExcel(MultipartFile file){
  7.         Response response = new Response();
  8.         response.ret(0,"成功了");
  9.         try {
  10.             CustomerImportDataHandler handler = new CustomerImportDataHandler(userMapper,tdCustomerMapper);
  11.             // 逐条插入
  12.             String processKey = ExcelImportMainTool.buildImport(CustomerImportDto.class, handler, redisTemplate).runAsyncSingle(file);
  13.             // 批量插入
  14.             // String processKey = ExcelImportMainTool.buildImport(CustomerImportDto.class, handler, redisTemplate).runAsyncMulti(file);
  15.             response.setData(processKey);
  16.         } catch (Exception e) {
  17.             e.printStackTrace();
  18.             response.ret(111000,"出错了");
  19.         }
  20.         return response;
  21.     }
  22.     /**
  23.      * 获取进度
  24.      * @return
  25.      */
  26.     @GetMapping("/getProgress")
  27.     public Response getProgress(String key){
  28.         Response response = new Response();
  29.         response.ret(0,"成功了");
  30.         try {
  31.             ImportProgress progress = ExcelImportMainTool.getProgress(redisTemplate, key);
  32.             response.setData(progress);
  33.         } catch (Exception e) {
  34.             e.printStackTrace();
  35.             response.ret(111001,"出错了");
  36.         }
  37.         return response;
  38.     }
复制代码
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表