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

文章目录

  • 前言
  • 一、控制器层代码
  • 二、服务层代码
  • 三、代码亮点分析


前言

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

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


一、控制器层代码

@RestController
public class SalesOrderController {@Resourceprivate SalesOrderExportService salesOrderExportService;@PostMapping(value = "/salesOrder/export")public void salesOrderExport(@RequestBody @Validated RequestDto req, HttpServletResponse response) {salesOrderExportService.salesOrderExport(req, response);}}

二、服务层代码

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

  1. 将多个Excel文件打包成ZIP文件
  2. 多线程ThreadPoolTaskExecutor并行处理销售订单的导出
@Slf4j
@Service
public class SalesOrderExportService {@Autowired@Qualifier("threadPoolTask")private ThreadPoolTaskExecutor threadPoolTaskExecutor;@Resourceprivate 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文件流到responsezipFileInputStream = 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>() {@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {Files.deleteIfExists(file);return FileVisitResult.CONTINUE;}@Overridepublic 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);}} 
  1. 使用EasyExcel库基于模板导出每个销售订单到单独的Excel文件中
    模板内容:
    在这里插入图片描述
/*** 导出单个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);}}

导出文件如下:
在这里插入图片描述

三、代码亮点分析

多线程处理:

  1. 通过CompletableFutureThreadPoolTaskExecutor,将销售订单的导出任务分配给多个线程并行执行,显著提高了处理大量订单时的性能。
  2. 使用Lists.partition方法将订单列表分割成多个子列表,每个子列表由一个线程处理,这里每5个订单一个线程。

Excel模板导出:

  1. 利用EasyExcel的模板功能,可以基于预定义的Excel模板填充数据,从而生成格式统一的销售订单Excel文件。
  2. 模板文件通过类加载器的getResourceAsStream方法加载,便维护。
  3. 将多个Excel文件打包成一个ZIP文件,方便用户下载和管理。

资源清理:

  1. 方法执行完毕后,及时关闭打开的文件流和删除临时生成的Excel文件和目录,避免了资源泄露。
  2. 使用try-with-resourcestry-catch-finally来确保资源的正确关闭和清理。

错误处理:

  1. 在方法执行过程中,对可能出现的异常进行了捕获和处理,确保服务的健壮。
  2. 对于无法恢复的错误,通过抛出运行时异常的方式通知调用者,并记录了详细的错误日志。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/56327.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

集成Spring Security详解

集成Spring Security详解 一、Spring Security简介 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架&#xff0c;它专注于为Java应用程序提供全面的安全解决方案。作为Spring项目的一部分&#xff0c;Spring Security继承了Spring框架的灵活性和可扩展性…

【图论】(四)最小生成树与拓扑排序

最小生成树与拓扑排序 最小生成树之prim&#xff08;P算法&#xff09;相关概念结题思路拓展 最小生成树之kruska&#xff08;K算法&#xff09;过程模拟程序实现拓展 拓扑排序背景与思路模拟过程程序实现 最小生成树之prim&#xff08;P算法&#xff09; 相关概念 P算法是用…

R语言数据分析案例:电影评分数据的探索性分析

# R语言数据分析案例&#xff1a;电影评分数据的探索性分析 在本案例中&#xff0c;我们将使用R语言对一份电影评分数据集进行探索性分析&#xff0c;主要包括数据清洗、可视化、描述性统计等环节&#xff0c;以了解数据的基本特征和趋势。假设我们使用的是一个电影评分数据集…

『完整代码』坐骑召唤

创建一个按钮 作为召唤/消失坐骑的开关 将预制体放入指定文件夹 命名为Mount01 创建脚本并编写&#xff1a;CallMount.cs using UnityEngine; using UnityEngine.UI; public class CallMount : MonoBehaviour{public Button callBtn;GameObject mountPrefab;GameObject mountIn…

CentOS7 上安装GitLab的经历

一、安装必要的基础环境 1.安装依赖包 [rootgitlab-server ~]#yum install curl policycoreutils openssh-server openssh-clients postfix wget git patch -y [rootgitlab-server ~]# systemctl start postfix 2.配置yum源(由于网络问题&#xff0c;国内用户请使用清华大学…

循序渐进丨MogDB 5.0 远程访问 MogDB/Oracle 数据库的简便方法(使用@符号)

概述 早期的 MogDB 就提供了Postgres_fdw、Oracle_fdw、MySQL_fdw3个插件&#xff0c;用于远程访问 MogDB/Oracle/MySQL数据库。 旧的版本中&#xff0c;访问远程数据库的表&#xff0c;需要显式创建外部表&#xff0c;而在 MogDB 5.0当中&#xff0c;这种用法得到了简化&…

导数的概念及在模型算法中的应用

一. 导数概念与计算 1. 导数的物理意义&#xff1a; 瞬时速率。一般的&#xff0c;函数yf(x)在x处的瞬时变化率是 2. 导数的几何意义&#xff1a; 曲线的切线&#xff0c;当点趋近于P时&#xff0c;直线 PT 与曲线相切。容易知道&#xff0c;割线的斜率是当点趋近于 P 时&…

数字孪生:引领智慧农业的未来

在现代农业中&#xff0c;数字化与智能化的浪潮正在改变传统的种植方式。数字孪生技术作为一种创新的数字化解决方案&#xff0c;正在深刻改变智慧农业的面貌&#xff0c;尤其是在大棚智能控制、数据全面可视、加工过程监控和物流运输溯源等方面展现出巨大的潜力。 frontop数字…

Golang使用viper读取配置到结构体,但是获取的数据为空

1.viper库 viper库是一个读取配置文件的库&#xff0c;支持多种配置文件&#xff0c;如JSON/TOML/YAML/HCL/envfile/Java properties 等 2.遇到的问题 在使用viper库的时候发现按照相应的配置已经读取到了对应的配置&#xff0c;但是转换为结构体的时候发现怎么拿结构体里面…

更改了 daemon.json ,需要重新启动docker容器吗

当你更改了 daemon.json 文件后&#xff0c;通常需要重启 Docker 服务以使更改生效&#xff0c;而不是重启单个 Docker 容器。以下是具体步骤&#xff1a; 保存并关闭 daemon.json 文件&#xff1a; 确保你已经完成了对 daemon.json 文件的修改并保存了更改。 重启 Docker 服务…

【动手学深度学习】8.1. 序列模型(个人向笔记)

想象一下有人正在看网飞&#xff08;Netflix&#xff0c;一个国外的视频网站&#xff09;上的电影。 一名忠实的用户会对每一部电影都给出评价&#xff0c; 毕竟一部好电影需要更多的支持和认可。 然而事实证明&#xff0c;事情并不那么简单。 随着时间的推移&#xff0c;人们对…

《Python基础教程》笔记(ch0-1)

前言 在Python生态系统中&#xff0c;各种包轮番登场&#xff0c;各种编码实践大行其道后又日渐式微。 引言 Python是什么&#xff1f;为何要使用它&#xff1f;官方宣传说&#xff1a;Python是一种面向对象的解释性高级编程语言&#xff0c;具有动态语义。 这句话的要点在…

监控易DEMO功能深度解析:运维行业的智能化转型新助力

在数字化转型的浪潮中&#xff0c;运维行业正面临着前所未有的变革与挑战。为了应对日益复杂的IT架构和不断提升的运维需求&#xff0c;监控易的集中式跨平台一体化监控软件不断升级优化&#xff0c;以适应新的运维环境。本文将对监控易DEMO的功能进行深度解析&#xff0c;探讨…

简单介绍冯诺依曼体系

现代的计算机, 大多遵守冯诺依曼体系结构 CPU中央处理器&#xff1a;进行算术运算和逻辑判断。存储器&#xff1a;分为外存和内存&#xff0c;用于存储数据&#xff08;使用二进制方式存储&#xff09;。输入设备&#xff1a;用户给计算机发号施令。输出设备&#xff1a;计算机…

Hadoop生态圈三大组件:HDFS的读写流程、MapReduce计算流程、Yarn资源调度

文章目录 1. HDFS的读写流程1.1 HDFS读流程1.2 HDFS写流程 2. MapReduce计算流程3. Yarn资源调度一、客户端请求资源二、Resource Manager处理请求三、任务资源计算与申请四、Resource Manager分配资源五、Node Manager执行任务六、任务执行与监控 1. HDFS的读写流程 1.1 HDFS…

沃德商协会管理系统小程序源码

商协会管理系统小程序&#xff0c;作为新一代数字化商协会运营管理的先锋工具&#xff0c;其核心围绕“智慧化会员体系、智敏化内容运营、智能化活动构建”三大核心板块精心构建。这一系统通过智慧化会员体系&#xff0c;实现了会员信息的精准管理与高效互动&#xff0c;不仅简…

2024_E_100_连续字母长度

连续字母长度 题目描述 给定一个字符串&#xff0c;只包含大写字母&#xff0c;求在包含同一字母的子串中&#xff0c;长度第 k 长的子串的长度&#xff0c;相同字母只取最长的那个子串。 输入描述 第一行有一个子串(1<长度<100)&#xff0c;只包含大写字母。 第二行为…

Rancher2.6管理k8s1.23

Rancher2.6管理k8s1.23 简介Rancher和k8s的区别 安装rancher初始化实验环境新增hosts文件条目安装docker 安装Rancher登录Rancher平台 通过Rancher仪表盘管理k8s集群&#xff1a;部署tomcat服务创建Ingress资源创建ingress规则 简介 Rancher是一个开源的企业级多集群Kubernete…

HarmonyOS 开发知识总结

1. HarmonyOS 开发知识总结 1.1. resources->base->media中不可以新建文件夹&#xff1f; 项目图片路径resources->base->media中不可以新建文件夹&#xff0c;图片全平级放里面&#xff0c;查找图片不方便&#xff0c;有没有什么其他的办法解决这个难点&#xff…

Scala入门基础(12)抽象类

抽象类&#xff0c;制定标准&#xff0c;不要求去具体实现 包含了抽象方法的类就是抽象类。抽象方法只是有方法名&#xff0c;没有具体方法体的方法 定义抽象类要用abstract&#xff08;抽象&#xff09;关键字 用智能驾驶技术举例&#xff1a;演示&#xff09…