Java+EasyExcel实现单个接口导出多个Excel的示例详解
一、核心问题与解决方案
在日常开发中,导出Excel是家常便饭,通常一个接口对应一个文件。但偶尔会遇到一些特殊场景——用户希望一次点击,就能同时拿到“用户列表”和“订单列表”这两份独立的数据报表。这该怎么实现呢?
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
这里有一个关键的技术前提需要明确:HTTP协议规定,单次响应只能返回一个字节流。这意味着,一个接口无法直接返回两个独立的文件。那么,如何绕过这个限制,满足用户“一次下载,多份文件”的需求呢?
答案其实很经典:打包。将多个Excel文件打包成一个ZIP压缩包,接口返回这个ZIP流。用户下载后解压,就能得到所有独立的文件。这个方案既完全遵守HTTP规范,又完美解决了业务需求,是业界通用且稳妥的做法。
二、前置准备:引入依赖
工欲善其事,必先利其器。实现这个功能,我们需要三个核心依赖:处理Excel读写的EasyExcel、负责文件打包的Apache Commons Compress,以及构建Web接口的Spring Boot Web Starter。以下是Ma ven配置,使用Gradle的朋友可以自行转换。版本号建议与示例保持一致,以避免潜在的兼容性问题。
com.alibaba easyexcel 4.0.3 org.apache.commons commons-compress 1.27.1 org.springframework.boot spring-boot-starter-web
三、步骤1:定义Excel对应的实体类
数据是Excel的灵魂,而实体类就是数据的蓝图。假设我们要导出用户和订单两份数据,就需要为它们分别创建实体类。通过EasyExcel的@ExcelProperty注解,可以轻松定义最终在Excel表头中显示的名称。这里使用Lombok的@Data注解来简化代码,省去手写getter/setter的麻烦。
3.1 用户实体类(UserData.ja va)
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 用户列表 Excel 对应的实体类
*/
@Data
public class UserData {
// Excel 表头:用户ID
@ExcelProperty("用户ID")
private Long userId;
// Excel 表头:用户名称
@ExcelProperty("用户名称")
private String userName;
// Excel 表头:手机号
@ExcelProperty("手机号")
private String phone;
}
3.2 订单实体类(OrderData.ja va)
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 订单列表 Excel 对应的实体类
*/
@Data
public class OrderData {
// Excel 表头:订单ID
@ExcelProperty("订单ID")
private String orderId;
// Excel 表头:用户ID(关联用户表)
@ExcelProperty("用户ID")
private Long userId;
// Excel 表头:订单金额
@ExcelProperty("订单金额")
private Double amount;
// Excel 表头:创建时间
@ExcelProperty("创建时间")
private String createTime;
}
四、步骤2:封装通用工具类(核心)
为了避免在每一个导出接口里重复编写打包和设置响应头的代码,封装一个通用的工具类是明智之举。这个ExcelZipExportUtil工具类将核心逻辑收拢,后续无论导出多少个Excel文件,都可以直接调用,代码复用率极高。
import com.alibaba.excel.EasyExcel;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import ja vax.servlet.ServletOutputStream;
import ja vax.servlet.http.HttpServletResponse;
import ja va.io.ByteArrayOutputStream;
import ja va.net.URLEncoder;
import ja va.util.List;
/**
* EasyExcel 多文件导出 ZIP 工具类(通用可复用)
*/
public class ExcelZipExportUtil {
/**
* 将单个 Excel 文件写入 ZIP 输出流
* @param zipOut ZIP 输出流
* @param excelFileName 单个 Excel 的文件名(如:用户列表.xlsx)
* @param data Excel 中的数据列表
* @param clazz Excel 对应的实体类(用于解析表头)
*/
public static void writeExcelToZip(ZipArchiveOutputStream zipOut, String excelFileName,
List data, Class clazz) throws Exception {
// 1. 临时存储 Excel 内容(内存级,不写入磁盘,性能更高)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 2. 使用 EasyExcel 写入数据(sheet1 是工作表名称,可自定义)
EasyExcel.write(bos, clazz)
// 不要自动关闭,交给 Servlet 自己处理
.autoCloseStream(false)
// 基于 column 长度,自动适配。最大 255 宽度
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 避免 Long 类型丢失精度
.registerConverter(new LongStringConverter())
// 工作表名称
.sheet("sheet1")
.doWrite(data);
// 3. 将 Excel 作为 ZIP 的一个条目写入
zipOut.putArchiveEntry(new ZipArchiveEntry(excelFileName));
zipOut.write(bos.toByteArray());
zipOut.closeArchiveEntry(); // 关闭当前 ZIP 条目(必须,否则后续条目无法写入)
// 4. 关闭临时流
bos.close();
}
/**
* 初始化 HTTP 响应头(设置 ZIP 下载、解决中文文件名乱码)
* @param response 响应对象
* @param zipFileName 最终下载的 ZIP 压缩包名称(如:用户订单数据.zip)
*/
public static void initZipResponse(HttpServletResponse response, String zipFileName) throws Exception {
// 设置响应类型为 ZIP
response.setContentType("application/zip");
// 设置下载头,URLEncoder.encode 解决中文文件名乱码(兼容所有浏览器)
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, "UTF-8"));
// 禁止缓存(避免浏览器缓存旧文件)
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
}
}
五、步骤3:接口层实现(最终落地)
工具类准备好后,接口层的实现就变得非常清晰。在Controller中,我们模拟构造数据(实际项目替换为数据库查询),然后调用工具类完成打包和响应。整个过程一气呵成,用户通过一次请求就能下载到包含多个Excel的ZIP包。
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ja vax.servlet.http.HttpServletResponse;
import ja va.util.ArrayList;
import ja va.util.List;
/**
* Excel 导出接口控制器
*/
@RestController
@RequestMapping("/export")
public class ExcelExportController {
/**
* 单个接口导出两个 Excel 文件(打包成 ZIP 下载)
* 访问地址:http://localhost:8080/export/twoExcel
*/
@GetMapping("/twoExcel")
public void exportTwoExcel(HttpServletResponse response) {
try {
// 1. 初始化响应头,设置 ZIP 压缩包名称(用户下载时显示的文件名)
ExcelZipExportUtil.initZipResponse(response, "用户订单数据.zip");
// 2. 获取 HTTP 响应输出流,关联 ZIP 输出流
ServletOutputStream servletOut = response.getOutputStream();
ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(servletOut);
// 3. 构造第一个 Excel 的数据(用户列表,实际项目中替换为数据库查询)
List userList = new ArrayList<>();
// 3.1 完善数据逻辑省略
// 4. 构造第二个 Excel 的数据(订单列表,实际项目中替换为数据库查询)
List orderList = new ArrayList<>();
// 4.1 完善数据逻辑省略
// 5. 关键操作:将两个 Excel 分别写入 ZIP 流
ExcelZipExportUtil.writeExcelToZip(zipOut, "用户列表.xlsx", userList, UserData.class);
ExcelZipExportUtil.writeExcelToZip(zipOut, "订单列表.xlsx", orderList, OrderData.class);
// 6. 关闭流(顺序不能错!否则 ZIP 包会损坏,无法解压)
zipOut.finish(); // 完成 ZIP 写入
zipOut.close();
servletOut.flush();
servletOut.close();
} catch (Exception e) {
e.printStackTrace();
// 实际项目中建议自定义异常处理,给前端返回明确的错误提示
response.setStatus(500);
}
}
}
六、关键注意事项(避坑重点)
这部分内容至关重要,很多开发者在实现后遇到ZIP包损坏、文件名乱码等问题,根源往往在于忽略了以下细节。
- 流的关闭顺序:这是最容易出错的地方。必须严格按照
zipOut.closeArchiveEntry()->zipOut.finish()-> 关闭其他流的顺序操作。顺序一旦颠倒,生成的ZIP包很可能无法正常解压。 - 中文文件名乱码:通过
URLEncoder.encode(fileName, "UTF-8")对文件名进行编码,这是确保在Chrome、Firefox、Edge等所有主流浏览器中都能正确显示中文名的标准做法。 - Excel写入方式:示例中采用了
ByteArrayOutputStream在内存中生成Excel内容,避免了不必要的磁盘I/O操作,性能更优。不推荐先写入临时磁盘文件再打包的方式。 - 异常处理:示例中的
printStackTrace()仅用于演示。在生产环境中,务必使用全局异常处理器或更优雅的方式,向前端返回友好的错误信息,例如“导出失败,请稍后重试”,以提升用户体验。
七、扩展:导出更多Excel文件
这个方案的扩展性极佳。如果需要导出三个、四个甚至更多Excel文件,完全无需修改工具类,只需在接口中继续调用ExcelZipExportUtil.writeExcelToZip()方法即可。
例如,新增一个“商品列表”的导出:
// 新增商品列表数据(假设已有 GoodsData 实体类) ListgoodsList = new ArrayList<>(); // ... 构造商品数据 // 新增一个 Excel 写入 ZIP 流 ExcelZipExportUtil.writeExcelToZip(zipOut, "商品列表.xlsx", goodsList, GoodsData.class);
八、测试效果验证
代码完成后,可以通过以下几个步骤验证功能是否正常:
- 启动Spring Boot项目,访问接口地址:
http://localhost:8080/export/twoExcel(端口号请根据实际配置调整); - 浏览器会自动弹出下载对话框,文件名为“用户订单数据.zip”;
- 下载完成后解压ZIP包,应该能得到“用户列表.xlsx”和“订单列表.xlsx”两个文件;
- 打开Excel文件,核对表头和数据是否与代码中模拟的测试数据一致。
九、总结
回顾整个方案,其实可以清晰地归纳为三个步骤:
- 准备:引入必要的依赖,定义好数据实体类。
- 封装:构建通用的ZIP打包工具类,处理响应头设置和文件写入逻辑。
- 调用:在业务接口中组织数据,调用工具类生成并返回ZIP流。
该方案设计通用,可直接复用于任何Spring Boot项目,能够支持任意数量的Excel文件导出。同时,它已经规避了流关闭顺序、中文乱码等常见“坑点”。如果你在项目中遇到类似的多文件导出需求,不妨直接采用这套经过验证的代码方案。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
Go语言中Struct Tag详解:XML解析必备的字段标签机制
Go语言Struct Tag深度解析:XML数据绑定与字段映射的核心机制 Struct Tag是Go语言为结构体字段附加元数据的核心语法,广泛应用于XML、JSON等数据序列化场景。它通过反引号包裹的键值对进行声明,本质上是指导编码器与解码器如何精确映射结构体字段与外部数据格式。缺少它,Go程序将无
c#如何调用Python脚本_c#Python脚本的最佳实践与常见坑点
C 调用Python脚本:最佳实践与常见坑点解析 使用 Process Start 调用 Python 脚本:最直接但需注意路径与环境 在大多数情况下,Process Start 是实现C 调用Python脚本最快捷的方案。它无需引入额外的NuGet包,也不强制要求Python解释器必须配置在系统环
c#如何定义常量_c#定义常量的3种方式
C 常量定义:const、static readonly与静态类的实战指南 在C 编程实践中,常量的定义是基础但至关重要的环节。选择不当的常量声明方式,可能会为项目引入难以察觉的隐患。本文将深入解析C 中定义常量的三种核心方式:const、static readonly以及使用静态类进行封装,帮助你
c#如何使用MEF框架_c#MEF框架的正确用法与注意事项
CompositionContainer 初始化失败常因类型反射加载失败,主因是程序集版本 框架不匹配、DLL未显式加载或缺失部署依赖;Import为null则多因Catalog未包含对应Export、路径错误或契约不一致。 为什么 CompositionContainer 初始化失败常报“Unab
C#怎么压缩并解压ZIP文件_C#如何管理压缩包【实战】
C 怎么压缩并解压ZIP文件_C 如何管理压缩包【实战】 说到在C 里处理ZIP文件,一个核心原则是:System IO Compression 是最稳妥的 ZIP 压缩方案。这意味着,你需要显式设置压缩级别为 CompressionLevel Optimal,使用正确的 ZipArchiveMod
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

