惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
Security Archives - TechRepublic
Security Archives - TechRepublic
C
Cyber Attacks, Cyber Crime and Cyber Security
Security Latest
Security Latest
Scott Helme
Scott Helme
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
C
CERT Recently Published Vulnerability Notes
S
Schneier on Security
G
GRAHAM CLULEY
L
Lohrmann on Cybersecurity
D
Darknet – Hacking Tools, Hacker News & Cyber Security
I
Intezer
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
F
Full Disclosure
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
WordPress大学
WordPress大学
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
大猫的无限游戏
大猫的无限游戏
MyScale Blog
MyScale Blog
Hacker News: Ask HN
Hacker News: Ask HN
G
Google Developers Blog
H
Heimdal Security Blog
O
OpenAI News
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LangChain Blog
C
Cisco Blogs
云风的 BLOG
云风的 BLOG
IT之家
IT之家
Cyberwarzone
Cyberwarzone
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Know Your Adversary
Know Your Adversary
博客园 - 聂微东
The Cloudflare Blog
C
Check Point Blog
K
Kaspersky official blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
月光博客
月光博客
T
Tor Project blog
T
Threat Research - Cisco Blogs
T
Tailwind CSS Blog
P
Proofpoint News Feed
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
A
About on SuperTechFans
小众软件
小众软件
Cloudbric
Cloudbric
A
Arctic Wolf

博客园_首页

Linux实操--组管理、权限管理和定时任务 Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 手机上的全能智能体,Auto小二正式开源 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12) Snack JSONPath 项目架构分析 Claude Code Buddy 小析:一个非核心功能,如何体现产品的细节完成度 AI新时代下的图床管理方案-Cloudflare图床+MCP+Skills方案指南 化繁为简:顺丰速运App如何通过 HarmonyOS SDK实现专业级空间测量 从零实现富文本编辑器#13-React非编辑节点的内容渲染 AI开发-python-langchain框架(3-23-OpenAI Functions风格Tool Calling智能助手) .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut PbootCMS 网站内容数量多导致访问慢?这些实用优化方案帮你提速! - 家兴网络技术工作室 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 网站漏洞怎么发现并修复?一篇实用指南(附完整流程) - 家兴网络技术工作室 开了 TUN 模式还是直连?90% 的人都踩过这个坑 Github日报|2026年04月12日 - AI一族 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术 Etsy 把 1000 个 MySQL 分片迁进 Vitess:425TB 数据背后的真正问题不是性能,而是运维规模 MicroPython LVGL基础知识和概念:底层渲染与性能优化 - FreakStudio 数据库草图算法 Python 潮流周刊#146:CPython 引入 Rust 的进展 - 豌豆花下猫 最小生成树 - mofei1116 红日靶场七:从外网入口、容器逃逸到 AD 接管的完整利用链复盘 - YouDiscovered1t 分享四款开源且实用的 Kafka 管理工具 - 追逐时光者 vLLM 权重加载机制全解析:从挑战到理想架构 LCT 学习笔记 - ACehomoxue Avalonia UI 12.0.0 正式发布:架构演进和性能飞跃 - 张善友 当 AI Agent 把调用链拉长,延迟开始成为一门生意 conhost.exe 无法显示 U+2717 - 145a 太秀了,我把自己蒸馏成了 Skill!已开源 - 程序员鱼皮 ASP.NET Core 内存缓存实战:一篇搞懂该怎么配、怎么避坑 基于 Ghostty 带有分割标签页和为 Claude 编程设计的通知终端 - BugShare AI 焊死入口:教育的“操作系统级”重塑 - 郝hai 初级Java开发工程师使用sql脚本编写代码的过程是简单而且不糊涂 - CoderOilStation Claude Code通关手册(六):MCP协议完全指南 - 暮色之狐 边框灯光环绕动画特效实现指南 - Newbe36524 开源:子木蒸馏版的 SEO 审计工具 seo-audit-skill v1.0 我所理解的Python元模型 【从0到1构建一个ClaudeAgent】规划与协调-TodoWrite - 程序员Seven Claude 和 Codex 在审计 Skill 上性能差异探究 - ACai_sec AScript如何实现中文脚本引擎 - rockey627 【渗透测试】HTB Season10 Garfield 全过程wp - dynasty_chenzi Android 开发者为什么必须掌握 AI 能力?端侧视角下的技术变革 树状数组正确性证明 - AC-wyr 你的 AI 焦虑,可能比 AI 本身更危险——ATM 机没有消灭银行柜员,但恐慌消灭了你的判断力 - 我没有三颗心脏 一个拉胯的分库分表方案有多绝望?整个部门都在救火! - 冰河团队 动态规划入门必学之走方格问题 - Ofnoname PostgREST 与 PostgreSQL 角色权限配置全解析(生产级实践) - SheepDog1998 使用 UEFI 图形输出协议 GOP 在屏幕上显示图像的方法 - 阿源- Claude Code通关手册(五):组建你的AI专家团队,子代理系统 - 暮色之狐 一个程序员到架构师的催婚路之感悟(整整10年后的催婚相亲感悟) - MisterLip 用 Agent Skill 自动生成工作周报 - 赵康
Java + EasyExcel 实现单个接口导出多个Excel
LucaJu · 2026-04-14 · via 博客园_首页

在日常开发中,我们经常会遇到 Excel 导出的需求,大多是单个接口导出单个 Excel 文件。但偶尔也会有特殊场景——需要一个接口同时导出两个(或多个)独立的 Excel 文件,比如同时导出“用户列表”和“订单列表”,方便用户一次性获取完整数据。

今天就基于 Spring Boot + EasyExcel(目前主流稳定版本),分享一种简单、通用、可直接落地的实现方案,全程附完整代码,新手也能快速上手~

一、核心问题与解决方案

首先要明确一个关键前提:HTTP 协议单次响应只能返回一个字节流,无法直接返回两个独立的 Excel 文件(相当于一次请求只能下载一个文件)。

那怎么实现“一个接口导出多个 Excel”?答案很简单——将多个 Excel 文件打包成 ZIP 压缩包,接口返回 ZIP 流,用户下载后解压,就能得到多个 Excel 文件。这是最通用、最合规的解决方案,既不违背 HTTP 协议,也能满足用户一次性获取多份文件的需求。

二、前置准备:引入依赖

首先在项目中引入 EasyExcel 核心依赖、ZIP 压缩依赖(用于打包多文件),以及 Spring Web 依赖(接口开发必备)。以下是 Maven 配置,Gradle 可对应转换,版本可根据自己项目需求微调(建议保持和示例一致,避免兼容问题)。

<!-- EasyExcel 核心依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
  	<version>4.0.3</version>
</dependency>

<!-- ZIP 压缩依赖(处理多文件打包) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-compress</artifactId>
    <version>1.27.1</version>
</dependency>

<!-- Spring Web 依赖(已有则忽略,接口开发必备) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

三、步骤1:定义Excel对应的实体类

假设我们需要导出两个 Excel 文件:用户列表订单列表,分别创建对应的实体类,通过 EasyExcel 的 @ExcelProperty 注解指定 Excel 表头名称(注解参数就是最终 Excel 中显示的表头)。

实体类用 Lombok 的 @Data 注解简化 getter/setter 代码,无需手动编写,节省开发时间。

3.1 用户实体类(UserData.java)

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.java)

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 写入 ZIP 流”和“初始化 HTTP 响应头”的逻辑。这个工具类是通用的,后续不管导出多少个 Excel,都能直接复用,无需重复编码。

import com.alibaba.excel.EasyExcel;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.net.URLEncoder;
import java.util.List;

/**
 * EasyExcel 多文件导出 ZIP 工具类(通用可复用)
 */
public class ExcelZipExportUtil {

    /**
     * 将单个 Excel 文件写入 ZIP 输出流
     * @param zipOut ZIP 输出流
     * @param excelFileName 单个 Excel 的文件名(如:用户列表.xlsx)
     * @param data Excel 中的数据列表
     * @param clazz Excel 对应的实体类(用于解析表头)
     */
    public static <T> void writeExcelToZip(ZipArchiveOutputStream zipOut, String excelFileName,
                                           List<T> data, Class<T> 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 的测试数据(实际项目中,这里可以替换成从数据库查询数据),然后调用上面封装的工具类,将两个 Excel 打包成 ZIP 流,通过 HttpServletResponse 返回,实现“一次请求下载两个 Excel”。

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 javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.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<UserData> userList = new ArrayList<>();
            // 3.1 完善数据逻辑省略

            // 4. 构造第二个 Excel 的数据(订单列表,实际项目中替换为数据库查询)
            List<OrderData> 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 包损坏、中文乱码、数据缺失等问题,大多是因为忽略了这些细节。

  1. 流的关闭顺序:必须先执行 zipOut.closeArchiveEntry()(关闭当前 ZIP 条目),再执行 zipOut.finish(),最后关闭 ZIP 流和响应流。顺序颠倒会导致 ZIP 包损坏,无法解压。
  2. 中文文件名乱码:通过 URLEncoder.encode(zipFileName, "UTF-8") 对 ZIP 文件名和 Excel 文件名编码,兼容 Chrome、Firefox、Edge 等所有主流浏览器,避免中文乱码。
  3. Excel 写入方式:示例中使用 ByteArrayOutputStream 临时存储 Excel 内容,属于内存级写入,不占用磁盘空间,性能更高。不建议直接写入磁盘文件再打包,会增加磁盘 IO 开销。
  4. 异常处理:实际项目中,建议替换 try-catch 中的 printStackTrace(),使用全局异常处理器,给前端返回明确的错误信息(如“导出失败,请重试”),提升用户体验。

七、扩展:导出更多Excel文件

如果需要导出 3 个、4 个甚至更多 Excel 文件,无需修改工具类,只需在接口中继续调用 ExcelZipExportUtil.writeExcelToZip() 方法即可。

示例(新增“商品列表”Excel):

// 新增商品列表数据(假设已有 GoodsData 实体类)
List<GoodsData&gt; goodsList = new ArrayList<>();
// ... 构造商品数据

// 新增一个 Excel 写入 ZIP 流
ExcelZipExportUtil.writeExcelToZip(zipOut, "商品列表.xlsx", goodsList, GoodsData.class);

八、测试效果验证

代码写完后,启动 Spring Boot 项目,通过以下步骤测试,确保导出正常:

  1. 访问接口地址:http://localhost:8080/export/twoExcel(端口号根据自己项目配置调整);
  2. 浏览器会自动弹出下载提示,下载的文件名为“用户订单数据.zip”;
  3. 解压 ZIP 压缩包,会得到两个 Excel 文件:用户列表.xlsx 和 订单列表.xlsx;
  4. 打开 Excel 文件,检查表头和数据是否正常(和接口中构造的测试数据一致)。

九、总结

代码通用可复用,总结下来就是 3 步:

  1. 引入 EasyExcel 和 ZIP 依赖,做好前置准备;
  2. 定义 Excel 对应的实体类,封装通用工具类(处理 ZIP 打包和响应头);
  3. 在接口中构造数据,调用工具类将多个 Excel 写入 ZIP 流,返回给前端。

该方案适用于所有 Spring Boot 项目,支持任意数量的 Excel 导出,避开了新手常踩的坑(流顺序、中文乱码),直接复制代码就能落地使用。如果你的项目中有类似需求,不妨试试这个方案~