SpringBoot中大量数据导出方案:使用EasyExcel并行导出多个excel文件并压缩zip后下载

简介: 在SpringBoot环境中,为了优化大量数据的Excel导出体验,可采用异步方式处理。具体做法是将数据拆分后利用`CompletableFuture`与`ThreadPoolTaskExecutor`并行导出,并使用EasyExcel生成多个Excel文件,最终将其压缩成ZIP文件供下载。此方案提升了导出效率,改善了用户体验。代码示例展示了如何实现这一过程,包括多线程处理、模板导出及资源清理等关键步骤。

SpringBoot的同步excel导出方式中,服务会阻塞直到Excel文件生成完毕,如果导出数据很多时,效率低体验差。有效的方案是将导出数据拆分后利用CompletableFuture,将导出任务异步化,并行使用easyExcel导出多个excel文件,最后将所有文件压缩成ZIP格式以方便下载。

Springboot环境下基于以上方案,下面代码的高质量的完成导出销售订单信息到Excel文件,并将多个Excel文件打包成一个ZIP文件,最后发送给客户端:

  1. 控制器层代码
@RestController
public class SalesOrderController {
    @Resource
    private SalesOrderExportService salesOrderExportService;
   @PostMapping(value = "/salesOrder/export")
    public void salesOrderExport(@RequestBody @Validated RequestDto req, HttpServletResponse response) {
        salesOrderExportService.salesOrderExport(req, response);
    }
 
}
  1. 服务层代码

负责执行销售订单的导出逻辑:

  • 1. 将多个Excel文件打包成ZIP文件
  • 2. 多线程ThreadPoolTaskExecutor并行处理销售订单的导出
@Slf4j
@Service
public class SalesOrderExportService {
 
    @Autowired
    @Qualifier("threadPoolTask")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
 
    @Resource
    private OrderManager OrderManager;
 
 
 public void salesOrderExport(RequestDto req, HttpServletResponse response) {
  
   // 获取导出数据,每个SalesOrder实例需要分别导出到一个excel文件
   List<SalesOrder> orderDataList = OrderManager.getOrder(req.getUserCode());
   // 略...校验数据
   
   InputStream zipFileInputStream = null;
   Path tempZipFilePath = null;
   Path tempDir = null;
   // 获取导出模板
   try (InputStream templateInputStream = this.getClass().getClassLoader().getResourceAsStream("template/order_template.xlsx");
     ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) {
     
    if (Objects.isNull(templateInputStream)) {
     throw new RuntimeException("获取模版文件异常");
    }
    // 多线程服用一个文件流
    IOUtils.copy(templateInputStream, outputStream);
    
    // 创建临时excel文件导出目录,用于将多个excel导出到此目录下
    Path tmpDirRef = (tempDir = Files.createTempDirectory(req.userCode() + "dir_prefix"));
     
    // 每5个salesOrder一个线程并行导出到excel文件中
    CompletableFuture[] salesOrderCf = Lists.partition(orderDataList, 5).stream()
    .map(orderDataSubList -> CompletableFuture
      .supplyAsync(() -> orderDataSubList.stream()
        .map(orderData -> this.exportExcelToFile(tmpDirRef, outputStream, orderData))
        .collect(Collectors.toList()), threadPoolTaskExecutor)
      .exceptionally(e -> {throw new RuntimeException(e);}))
    .toArray(CompletableFuture[]::new);
   
    // 等待所有excel文件导出完成
    CompletableFuture.allOf(salesOrderCf).get(3, TimeUnit.MINUTES);
    
    // 创建临时zip文件
    tempZipFilePath = Files.createTempFile(req.userCode() + TMP_ZIP_DIR_PRE, ".zip");
    
    // 将excel目录下的所有文件压缩到zip文件中,zipUtil有很多工具包都有
    ZipUtil.zip(tempDir.toString(), tempZipFilePath.toString());
    
    response.setContentType("application/octet-stream;charset=UTF-8");
    response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(tempZipFilePath.toFile().getName(), "utf-8"));
    
    // 写zip文件流到response
    zipFileInputStream = Files.newInputStream(tempZipFilePath);
    IOUtils.copy(zipFileInputStream, response.getOutputStream());
   } catch (Exception e) {
    log.error("salesOrderExport,异常:", e);
    throw new RuntimeException("导出异常,请稍后重拾");
   } finally {
      try {  
         // 关闭流
        if (Objects.nonNull(zipFileInputStream)) {
           zipFileInputStream.close();
         }    
        // 删除临时文件及目录
        if (Objects.nonNull(tempDir)) {
           Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {
              @Override
              public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                   Files.deleteIfExists(file);
                   return FileVisitResult.CONTINUE;
              }
             @Override
             public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                 Files.deleteIfExists(dir);
                 return FileVisitResult.CONTINUE;
              }
            });
        }
        if (Objects.nonNull(tempZipFilePath)) {
          Files.deleteIfExists(tempZipFilePath);
        }
    } catch (Exception e) {
        log.error("salesOrderExport, 关闭文件流失败:", e);
    }
   }
  • 使用EasyExcel库基于模板导出每个销售订单到单独的Excel文件中

模板内容:

fea938c519b263d06a5c890f8ba4be5.png

/**
   * 导出单个excle文件,上面的多线程代码调用
   **/
 private Path exportExcelToFile(Path temporaryDir, ByteArrayOutputStream templateOutputStream, SalesOrder data) {
     Path temproaryFilePath = null;
     try {
         // 创建临时文件 
         temproaryFilePath = Files.createTempFile(temporaryDir, data.getOrderNo(), ExcelTypeEnum.XLSX.getValue());
     } catch (IOException e) {
         throw new RuntimeException("exportExcelToFile,创建excel临时文件失败:" + data.getOrderNo());
     }
     try (InputStream templateInputStream = new ByteArrayInputStream(templateOutputStream.toByteArray());
          OutputStream temporaryFileOs = Files.newOutputStream(temproaryFilePath);
          BufferedOutputStream tempOutStream = new BufferedOutputStream(temporaryFileOs)) {
    
        // 使用easyExcel的模板功能导出订单数据到临时文件中 
         ExcelWriter excelWriter = EasyExcel.write(tempOutStream, SalesOrder.class)
                 .withTemplate(templateInputStream).excelType(ExcelTypeEnum.XLSX).build();
   
         // 填充模板数据
         WriteSheet writeSheet = EasyExcel.writerSheet().build();
         FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
         excelWriter.fill(new FillWrapper("goods", data.getGoodsList()), fillConfig, writeSheet);
         excelWriter.fill(data, writeSheet);
         excelWriter.finish();
         // temproaryFilePath.toFile().deleteOnExit();
         return temproaryFilePath;
     } catch (Exception e) {
         throw new RuntimeException("exportExcelToFile,导出excel文件失败:" + data.getOrderNo(), e);
     }
 }

导出文件如下:

538a2eb0dbbd35c5aa8172596a897a0.png

代码亮点分析
  1. 多线程处理
  • 通过CompletableFutureThreadPoolTaskExecutor,将销售订单的导出任务分配给多个线程并行执行,显著提高了处理大量订单时的性能。
  • 使用Lists.partition方法将订单列表分割成多个子列表,每个子列表由一个线程处理,这里每5个订单一个线程。
  1. Excel模板导出
  • 利用EasyExcel的模板功能,可以基于预定义的Excel模板填充数据,从而生成格式统一的销售订单Excel文件。
  • 模板文件通过类加载器的getResourceAsStream方法加载,便维护。
  • 将多个Excel文件打包成一个ZIP文件,方便用户下载和管理。
  1. 资源清理
  • 方法执行完毕后,及时关闭打开的文件流和删除临时生成的Excel文件和目录,避免了资源泄露。
  • 使用try-with-resourcestry-catch-finally来确保资源的正确关闭和清理。
  1. 错误处理
  • 在方法执行过程中,对可能出现的异常进行了捕获和处理,确保服务的健壮。
  • 对于无法恢复的错误,通过抛出运行时异常的方式通知调用者,并记录了详细的错误日志。
相关文章
|
10月前
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——封装统一返回的数据结构
本文介绍了在Spring Boot中封装统一返回的数据结构的方法。通过定义一个泛型类`JsonResult&lt;T&gt;`,包含数据、状态码和提示信息三个属性,满足不同场景下的JSON返回需求。例如,无数据返回时可设置默认状态码&quot;0&quot;和消息&quot;操作成功!&quot;,有数据返回时也可自定义状态码和消息。同时,文章展示了如何在Controller中使用该结构,通过具体示例(如用户信息、列表和Map)说明其灵活性与便捷性。最后总结了Spring Boot中JSON数据返回的配置与实际项目中的应用技巧。
822 0
|
10月前
|
JSON Java fastjson
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——使用 fastJson 处理 null
本文介绍如何使用 fastJson 处理 null 值。与 Jackson 不同,fastJson 需要通过继承 `WebMvcConfigurationSupport` 类并覆盖 `configureMessageConverters` 方法来配置 null 值的处理方式。例如,可将 String 类型的 null 转为 &quot;&quot;,Number 类型的 null 转为 0,避免循环引用等。代码示例展示了具体实现步骤,包括引入相关依赖、设置序列化特性及解决中文乱码问题。
553 0
|
10月前
|
JSON Java fastjson
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——Spring Boot 默认对Json的处理
本文介绍了在Spring Boot中返回Json数据的方法及数据封装技巧。通过使用`@RestController`注解,可以轻松实现接口返回Json格式的数据,默认使用的Json解析框架是Jackson。文章详细讲解了如何处理不同数据类型(如类对象、List、Map)的Json转换,并提供了自定义配置以应对null值问题。此外,还对比了Jackson与阿里巴巴FastJson的特点,以及如何在项目中引入和配置FastJson,解决null值转换和中文乱码等问题。
1543 0
|
6月前
|
JSON Java 数据格式
Spring Boot返回Json数据及数据封装
在Spring Boot中,接口间及前后端的数据传输通常使用JSON格式。通过@RestController注解,可轻松实现Controller返回JSON数据。该注解是Spring Boot新增的组合注解,结合了@Controller和@ResponseBody的功能,默认将返回值转换为JSON格式。Spring Boot底层默认采用Jackson作为JSON解析框架,并通过spring-boot-starter-json依赖集成了相关库,包括jackson-databind、jackson-datatype-jdk8等常用模块,简化了开发者对依赖的手动管理。
663 3
|
6月前
|
Python
如何根据Excel某列数据为依据分成一个新的工作表
在处理Excel数据时,我们常需要根据列值将数据分到不同的工作表或文件中。本文通过Python和VBA两种方法实现该操作:使用Python的`pandas`库按年级拆分为多个文件,再通过VBA宏按班级生成新的工作表,帮助高效整理复杂数据。
|
6月前
|
数据采集 数据可视化 数据挖掘
用 Excel+Power Query 做电商数据分析:从 “每天加班整理数据” 到 “一键生成报表” 的配置教程
在电商运营中,数据是增长的关键驱动力。然而,传统的手工数据处理方式效率低下,耗费大量时间且易出错。本文介绍如何利用 Excel 中的 Power Query 工具,自动化完成电商数据的采集、清洗与分析,大幅提升数据处理效率。通过某美妆电商的实战案例,详细拆解从多平台数据整合到可视化报表生成的全流程,帮助电商从业者摆脱繁琐操作,聚焦业务增长,实现数据驱动的高效运营。
|
8月前
|
存储 安全 大数据
网安工程师必看!AiPy解决fscan扫描数据整理难题—多种信息快速分拣+Excel结构化存储方案
作为一名安全测试工程师,分析fscan扫描结果曾是繁琐的手动活:从海量日志中提取开放端口、漏洞信息和主机数据,耗时又易错。但现在,借助AiPy开发的GUI解析工具,只需喝杯奶茶的时间,即可将[PORT]、[SERVICE]、[VULN]、[HOST]等关键信息智能分类,并生成三份清晰的Excel报表。告别手动整理,大幅提升效率!在安全行业,工具党正碾压手动党。掌握AiPy,把时间留给真正的攻防实战!官网链接:https://www.aipyaipy.com,解锁更多用法!
|
数据采集 数据可视化 数据挖掘
利用Python自动化处理Excel数据:从基础到进阶####
本文旨在为读者提供一个全面的指南,通过Python编程语言实现Excel数据的自动化处理。无论你是初学者还是有经验的开发者,本文都将帮助你掌握Pandas和openpyxl这两个强大的库,从而提升数据处理的效率和准确性。我们将从环境设置开始,逐步深入到数据读取、清洗、分析和可视化等各个环节,最终实现一个实际的自动化项目案例。 ####
2262 10
|
6月前
|
Python
将Excel特定某列数据删除
将Excel特定某列数据删除
|
数据采集 存储 JavaScript
自动化数据处理:使用Selenium与Excel打造的数据爬取管道
本文介绍了一种使用Selenium和Excel结合代理IP技术从WIPO品牌数据库(branddb.wipo.int)自动化爬取专利信息的方法。通过Selenium模拟用户操作,处理JavaScript动态加载页面,利用代理IP避免IP封禁,确保数据爬取稳定性和隐私性。爬取的数据将存储在Excel中,便于后续分析。此外,文章还详细介绍了Selenium的基本设置、代理IP配置及使用技巧,并探讨了未来可能采用的更多防反爬策略,以提升爬虫效率和稳定性。
773 4