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. 错误处理
  • 在方法执行过程中,对可能出现的异常进行了捕获和处理,确保服务的健壮。
  • 对于无法恢复的错误,通过抛出运行时异常的方式通知调用者,并记录了详细的错误日志。
目录
打赏
0
8
6
0
102
分享
相关文章
|
20天前
|
springboot-多环境配置文件
本文介绍了如何创建开发和生产环境的配置文件,并在IDEA和Maven中进行配置。开发环境中,通过设置profile为`dev`来指定配置文件;生产环境中,使用Maven命令参数`-Pprod`打包并指定配置文件。公共配置可放在`application.yml`中统一管理。日志配置需确保`logback-spring.xml`中的profile正确,以保证日志正常输出。
springboot-多环境配置文件
Springboot静态资源映射及文件映射
在Spring Boot项目中,为了解决前端访问后端存储的图片问题,起初尝试通过静态资源映射实现,但发现这种方式仅能访问打包时已存在的文件。对于动态上传的图片(如头像),需采用资源映射配置,将特定路径映射到服务器上的文件夹,确保新上传的图片能即时访问。例如,通过`addResourceHandler(&quot;/img/**&quot;).addResourceLocations(&quot;file:E:\\myProject\\forum_server\\&quot;)`配置,使前端可通过URL直接访问图片。
Springboot静态资源映射及文件映射
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
【全自动改PDF名】批量OCR识别提取PDF自定义指定区域内容保存到 Excel 以及根据PDF文件内容的标题来批量重命名
学校和教育机构常需处理成绩单、报名表等PDF文件。通过OCR技术,可自动提取学生信息并录入Excel,便于统计分析和存档管理。本文介绍使用阿里云服务实现批量OCR识别、内容提取、重命名及导出表格的完整步骤,包括开通相关服务、编写代码、部署函数计算和设置自动化触发器等。提供Python示例代码和详细操作指南,帮助用户高效处理PDF文件。 链接: - 百度网盘:[链接](https://pan.baidu.com/s/1mWsg7mDZq2pZ8xdKzdn5Hg?pwd=8866) - 腾讯网盘:[链接](https://share.weiyun.com/a77jklXK)
78 5
【图片型PDF】批量识别扫描件PDF指定区域局部位置内容,将识别内容导出Excel表格或批量改名文件,基于阿里云OCR对图片型PDF识别改名案例实现
在医疗和政务等领域,图片型PDF文件(如病历、报告、公文扫描件)的处理需求广泛。通过OCR技术识别这些文件中的文字信息,提取关键内容并保存为表格,极大提高了信息管理和利用效率。本文介绍一款工具——咕嘎批量OCR系统,帮助用户快速处理图片型PDF文件,支持区域识别、内容提取、导出表格及批量改名等功能。下载工具后,按步骤选择处理模式、进行区域采样、批量处理文件,几分钟内即可高效完成数百个文件的处理。
157 8
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
403 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
3月前
|
按条件将Excel文件拆分到不同的工作表
使用Python的pandas库,可以轻松将Excel文件按条件拆分到不同的工作表中。本文通过一个示例代码展示了如何生成一个包含总成绩表和三个班级表的Excel文件。代码首先创建了一个包含学生姓名、班级和各科成绩的数据框,然后按班级分组,将每个班级的数据分别写入不同的工作表。最后,生成的Excel文件将包含四个工作表,分别为总成绩表和三个班级的成绩表。
59 6
按条件将Excel文件拆分到不同的工作表
|
3月前
|
批量将不同的工作簿合并到同一个Excel文件
本文介绍如何使用Python的`pandas`库批量合并不同工作簿至同一Excel文件。通过模拟生成三个班级的成绩数据,分别保存为Excel文件,再将这些文件合并成一个包含所有班级成绩的总成绩单。步骤包括安装必要库、生成数据、保存与合并工作簿。
88 6
|
3月前
|
按条件将Excel文件拆分到不同的工作表
使用Python的pandas库,可以轻松将Excel文件按条件拆分为多个工作表。本文通过一个具体示例,展示了如何根据学生班级将成绩数据拆分到不同的工作表中,并生成一个包含总成绩表和各班级成绩表的Excel文件。代码简洁明了,适合初学者学习和应用。
76 6
|
4月前
|
实现Excel文件和其他文件导出为压缩包,并导入
实现Excel文件和其他文件导出为压缩包,并导入
67 1

热门文章

最新文章