当前位置: 首页 > news >正文

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. 对于无法恢复的错误,通过抛出运行时异常的方式通知调用者,并记录了详细的错误日志。

相关文章:

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

文章目录 前言一、控制器层代码二、服务层代码三、代码亮点分析 前言 SpringBoot的同步excel导出方式中&#xff0c;服务会阻塞直到Excel文件生成完毕&#xff0c;如果导出数据很多时&#xff0c;效率低体验差。有效的方案是将导出数据拆分后利用CompletableFuture&#xff0c;…...

黑马软件测试第一篇_数据库

说明: 数据库是专门用来存储数据的软件 注意: 对于测试工作而言, 如果项目页面没有实现, 但是我们又想要校验数据,则可以直接通过查询数据库实现 关系: 具体存在的商品录入后 -> 产生对应的数据(存到数据库中) -> 最后会被加载到项目页面中 数据库的分类 分类: 1> 关…...

第十六届蓝桥杯嵌入式组准备

最近我看很多人都在准备蓝桥杯的比赛了&#xff0c;这里我给大家整理一下历届真题或模拟题的讲解与源码 蓝桥杯嵌入式第十二届省赛真题二 蓝桥杯嵌入式第十三届省赛真题一 蓝桥杯嵌入式第十三届省赛真题二 蓝桥杯嵌入式第十四届省赛真题 蓝桥杯嵌入式第十四届模拟考试一 蓝…...

城乡供水信息化系统如何建设?

城乡供水信息化建设是一个综合性的过程&#xff0c;旨在通过现代信息技术提升农村供水系统的管理效率和服务质量。这一过程包含以下关键内容&#xff1a; 一、信息化基础设施建设 感知层建设&#xff1a;在农村饮水工程的关键部位&#xff0c;如水源地、水厂、供水管网等&#…...

【Petri网导论学习笔记】Petri网导论入门学习(七) —— 1.5 并发与冲突

导航 1.5 并发与冲突1.5.1 并发定义 1.14定义 1.15 1.5.2 冲突定义 1.17 1.5.3 一般Petri网系统中的并发与冲突定义 1.18一般网系统中无冲撞概念阻塞&#xff08;有容量函数K的P/T系统&#xff0c;类似于冲撞&#xff09;一般Petri网中并发与冲突共存情况 1.5 并发与冲突 Petr…...

MongoDB常用语句

1.只统计记录总数&#xff1a; let result await CorrectionRecordModel.countDocuments(db);2.数组遍历&#xff0c;循环体中可以有调用异步函数&#xff1a; for(let item of result2){if(item && Tool.checkNotEmptString(item.auth_id) && (item.status …...

自动创作PPT 利用提示词和大模型自动创建ppt

背景 ppt创作可以分为3个步骤&#xff1a;1.大纲撰写&#xff1b;2.内容填充&#xff1b;3.ppt实现。我前几天用十分钟的时间做了一个ppt&#xff0c;主讲大模型测评。这里给大家分享一下我的创作过程。 关于步骤1和步骤2&#xff0c;最近发现一个非常好的提示词&#xff0c;…...

二分类评价指标AUROC和AUPR

文章目录 一、AUROC&#xff08;Area Under the Receiver Operating Characteristic Curve&#xff09;二、AUPR&#xff08;Area Under the Precision-Recall Curve&#xff09;三、区别3.1 案例3.2 如何选择&#xff1f; 在分类任务中&#xff0c; AUROC&#xff08;受试者工…...

雅迪控股营收、净利润和毛利下滑:销量大幅减少,屡屡抽查不合格

《港湾商业观察》廖紫雯 日前&#xff0c;雅迪集团控股有限公司&#xff08;以下简称&#xff1a;雅迪控股&#xff0c;01585.HK&#xff09;发布业绩报告&#xff0c;披露2024年上半年营收净利双下滑等情况&#xff0c;在业绩承压的情况下&#xff0c;雅迪控股遭多家券商下调…...

【网络安全】记一次漏洞挖掘

Spring Cloud Data Flow 热点漏洞详细分析 环境搭建 2.10.0 - 2.11.2版本都可以&#xff0c;这里下的2.11.2 源码下载https://github.com/spring-cloud/spring-cloud-dataflow/tree/v2.11.2 在src/docker-compose里面是有docker文件的&#xff0c;使用docker即可 最近是爆出…...

Redis遇到Hash冲突怎么办?

这是小伙伴之前遇到的一个面试题&#xff0c;感觉也是一个经典八股&#xff0c;和大伙分享下。 一 什么是 Hash 冲突 Hash 冲突&#xff0c;也称为 Hash 碰撞&#xff0c;是指不同的关键字通过 Hash 函数计算得到了相同的 Hash 地址。 Hash 冲突在 Hash 表中是不可避免的&am…...

React综合指南(四)

61、描述React事件处理。 为了解决跨浏览器兼容性问题&#xff0c;React中的事件处理程序将传递SyntheticEvent实例&#xff0c;该实例是React跨浏览器本机事件的跨浏览器包装器。这些综合事件具有与您惯用的本机事件相同的界面&#xff0c;除了它们在所有浏览器中的工作方式相…...

Spring集成Redisson及存取几种基本类型数据

目录 一.什么是Redisson 二.为什么要使用Redisson 三.Spring集成Redisson 1.添加依赖 2.添加配置信息 3.添加redisson配置类 四.Redisson存取各种类型数据 1.字符串(String类型) 存储 获取 2.object对象类型 1.实体类信息 2.存储 3.获取 3.List集合类型 第一种…...

Maplibre-gl\Mapbox-gl改造支持对矢量瓦片加密

Maplibre-gl是Mapbox-gl剔除自带地图服务之后的一个分支,代码很相似。Maplibre-gl\Mapbox-gl使用的pbf格式的矢量瓦片,数据量小,渲染效果好。但也存在着信息泄露的风险。但如果想使用这个开发框架的前端渲染效果,还必须要使用这个格式。最近研究了一下如何对矢量瓦片进行加…...

【功能安全】技术安全概念TSC

目录 01 TSC定义 02 TSC注意事项 03 TSC案例 📖 推荐阅读 01 TSC定义 所处位置 TSC:Technical safety concept技术安全概念 TSR:Technical safety requirement技术安全需求 在系统开发阶段属于安全活动4-6 系统层产品开发示例 TSC目的...

Spark数据源的读取与写入、自定义函数

1. 数据源的读取与写入 1.1 数据读取 读文件 read.jsonread.csv csv文件由两个部分组成&#xff1a;头部数据&#xff08;也就是字段数据&#xff09;、行数据。 read.orc 读数据库 read.jdbc(jdbc连接地址,table‘表名’,properties{‘user’用户名,‘password’密码,‘driv…...

LeetCode 每日一题 2024/10/14-2024/10/20

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录 10/14 887. 鸡蛋掉落10/15 3200. 三角形的最大高度10/16 3194. 最小元素和最大元素的最小平均值10/17 3193. 统计逆序对的数目10/18 3191. 使二进制数组全部等于 1 的最少操…...

接口测试(六)jmeter——参数化(配置元件 --> 用户定义的变量)

一、jmeter——参数化&#xff08;配置元件 --> 用户定义的变量&#xff09; 注&#xff1a;示例仅供参考 1. 参数化格式&#xff1a;${变量名} 2. 配置元件&#xff1a;用户定义的变量 3. 添加【用户定义的变量】&#xff0c;【线程组】–>【添加】–>【配置元件】–…...

【学习笔记】网络流

背景 马上ICPC了&#xff0c;很惊奇的发现自己没整理网络流的板子。 最大流 dinic 这里选用的是二分图最大匹配的板子&#xff1a;飞行员配对方案问题 #include<bits/stdc.h> #define int long long using namespace std; const int N1e67,inf1e18; struct E {int to…...

【鸡翅Club】项目启动

一、项目背景 这是一个 C端的社区项目&#xff0c;有博客、交流&#xff0c;面试学习&#xff0c;练题等模块。 项目的背景主要是我们想要通过面试题的分类&#xff0c;难度&#xff0c;打标&#xff0c;来评估员工的技术能力。同时在我们公司招聘季的时候&#xff0c;极大的…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文通过代码驱动的方式&#xff0c;系统讲解PyTorch核心概念和实战技巧&#xff0c;涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

C++--string的模拟实现

一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现&#xff0c;其目的是加强对string的底层了解&#xff0c;以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量&#xff0c;…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”

非常好&#xff0c;我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题&#xff0c;统一使用 二重复合函数&#xff1a; z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y))​ 来全面说明。我们会展示其全微分形式&#xff08;偏导…...