Java+EasyExcel實(shí)現(xiàn)單個接口導(dǎo)出多個Excel的示例詳解
在日常開發(fā)中,我們經(jīng)常會遇到 Excel 導(dǎo)出的需求,大多是單個接口導(dǎo)出單個 Excel 文件。但偶爾也會有特殊場景——需要一個接口同時導(dǎo)出兩個(或多個)獨(dú)立的 Excel 文件,比如同時導(dǎo)出“用戶列表”和“訂單列表”,方便用戶一次性獲取完整數(shù)據(jù)。
今天就基于 Spring Boot + EasyExcel(目前主流穩(wěn)定版本),分享一種簡單、通用、可直接落地的實(shí)現(xiàn)方案,全程附完整代碼,新手也能快速上手~
一、核心問題與解決方案
首先要明確一個關(guān)鍵前提:HTTP 協(xié)議單次響應(yīng)只能返回一個字節(jié)流,無法直接返回兩個獨(dú)立的 Excel 文件(相當(dāng)于一次請求只能下載一個文件)。
那怎么實(shí)現(xiàn)“一個接口導(dǎo)出多個 Excel”?答案很簡單——將多個 Excel 文件打包成 ZIP 壓縮包,接口返回 ZIP 流,用戶下載后解壓,就能得到多個 Excel 文件。這是最通用、最合規(guī)的解決方案,既不違背 HTTP 協(xié)議,也能滿足用戶一次性獲取多份文件的需求。
二、前置準(zhǔn)備:引入依賴
首先在項(xiàng)目中引入 EasyExcel 核心依賴、ZIP 壓縮依賴(用于打包多文件),以及 Spring Web 依賴(接口開發(fā)必備)。以下是 Maven 配置,Gradle 可對應(yīng)轉(zhuǎn)換,版本可根據(jù)自己項(xiàng)目需求微調(diào)(建議保持和示例一致,避免兼容問題)。
<!-- 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 依賴(已有則忽略,接口開發(fā)必備) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>三、步驟1:定義Excel對應(yīng)的實(shí)體類
假設(shè)我們需要導(dǎo)出兩個 Excel 文件:用戶列表 和 訂單列表,分別創(chuàng)建對應(yīng)的實(shí)體類,通過 EasyExcel 的 @ExcelProperty 注解指定 Excel 表頭名稱(注解參數(shù)就是最終 Excel 中顯示的表頭)。
實(shí)體類用 Lombok 的 @Data 注解簡化 getter/setter 代碼,無需手動編寫,節(jié)省開發(fā)時間。
3.1 用戶實(shí)體類(UserData.java)
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 用戶列表 Excel 對應(yīng)的實(shí)體類
*/
@Data
public class UserData {
// Excel 表頭:用戶ID
@ExcelProperty("用戶ID")
private Long userId;
// Excel 表頭:用戶名稱
@ExcelProperty("用戶名稱")
private String userName;
// Excel 表頭:手機(jī)號
@ExcelProperty("手機(jī)號")
private String phone;
}
3.2 訂單實(shí)體類(OrderData.java)
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 訂單列表 Excel 對應(yīng)的實(shí)體類
*/
@Data
public class OrderData {
// Excel 表頭:訂單ID
@ExcelProperty("訂單ID")
private String orderId;
// Excel 表頭:用戶ID(關(guān)聯(lián)用戶表)
@ExcelProperty("用戶ID")
private Long userId;
// Excel 表頭:訂單金額
@ExcelProperty("訂單金額")
private Double amount;
// Excel 表頭:創(chuàng)建時間
@ExcelProperty("創(chuàng)建時間")
private String createTime;
}
四、步驟2:封裝通用工具類(核心)
為了避免接口層代碼冗余,我們封裝一個工具類 ExcelZipExportUtil,專門處理“將多個 Excel 寫入 ZIP 流”和“初始化 HTTP 響應(yīng)頭”的邏輯。這個工具類是通用的,后續(xù)不管導(dǎo)出多少個 Excel,都能直接復(fù)用,無需重復(fù)編碼。
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 多文件導(dǎo)出 ZIP 工具類(通用可復(fù)用)
*/
public class ExcelZipExportUtil {
/**
* 將單個 Excel 文件寫入 ZIP 輸出流
* @param zipOut ZIP 輸出流
* @param excelFileName 單個 Excel 的文件名(如:用戶列表.xlsx)
* @param data Excel 中的數(shù)據(jù)列表
* @param clazz Excel 對應(yīng)的實(shí)體類(用于解析表頭)
*/
public static <T> void writeExcelToZip(ZipArchiveOutputStream zipOut, String excelFileName,
List<T> data, Class<T> clazz) throws Exception {
// 1. 臨時存儲 Excel 內(nèi)容(內(nèi)存級,不寫入磁盤,性能更高)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 2. 使用 EasyExcel 寫入數(shù)據(jù)(sheet1 是工作表名稱,可自定義)
EasyExcel.write(bos, clazz)
// 不要自動關(guān)閉,交給 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(); // 關(guān)閉當(dāng)前 ZIP 條目(必須,否則后續(xù)條目無法寫入)
// 4. 關(guān)閉臨時流
bos.close();
}
/**
* 初始化 HTTP 響應(yīng)頭(設(shè)置 ZIP 下載、解決中文文件名亂碼)
* @param response 響應(yīng)對象
* @param zipFileName 最終下載的 ZIP 壓縮包名稱(如:用戶訂單數(shù)據(jù).zip)
*/
public static void initZipResponse(HttpServletResponse response, String zipFileName) throws Exception {
// 設(shè)置響應(yīng)類型為 ZIP
response.setContentType("application/zip");
// 設(shè)置下載頭,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:接口層實(shí)現(xiàn)(最終落地)
創(chuàng)建 Controller 接口,模擬構(gòu)造兩個 Excel 的測試數(shù)據(jù)(實(shí)際項(xiàng)目中,這里可以替換成從數(shù)據(jù)庫查詢數(shù)據(jù)),然后調(diào)用上面封裝的工具類,將兩個 Excel 打包成 ZIP 流,通過 HttpServletResponse 返回,實(shí)現(xiàn)“一次請求下載兩個 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 導(dǎo)出接口控制器
*/
@RestController
@RequestMapping("/export")
public class ExcelExportController {
/**
* 單個接口導(dǎo)出兩個 Excel 文件(打包成 ZIP 下載)
* 訪問地址:http://localhost:8080/export/twoExcel
*/
@GetMapping("/twoExcel")
public void exportTwoExcel(HttpServletResponse response) {
try {
// 1. 初始化響應(yīng)頭,設(shè)置 ZIP 壓縮包名稱(用戶下載時顯示的文件名)
ExcelZipExportUtil.initZipResponse(response, "用戶訂單數(shù)據(jù).zip");
// 2. 獲取 HTTP 響應(yīng)輸出流,關(guān)聯(lián) ZIP 輸出流
ServletOutputStream servletOut = response.getOutputStream();
ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(servletOut);
// 3. 構(gòu)造第一個 Excel 的數(shù)據(jù)(用戶列表,實(shí)際項(xiàng)目中替換為數(shù)據(jù)庫查詢)
List<UserData> userList = new ArrayList<>();
// 3.1 完善數(shù)據(jù)邏輯省略
// 4. 構(gòu)造第二個 Excel 的數(shù)據(jù)(訂單列表,實(shí)際項(xiàng)目中替換為數(shù)據(jù)庫查詢)
List<OrderData> orderList = new ArrayList<>();
// 4.1 完善數(shù)據(jù)邏輯省略
// 5. 關(guān)鍵操作:將兩個 Excel 分別寫入 ZIP 流
ExcelZipExportUtil.writeExcelToZip(zipOut, "用戶列表.xlsx", userList, UserData.class);
ExcelZipExportUtil.writeExcelToZip(zipOut, "訂單列表.xlsx", orderList, OrderData.class);
// 6. 關(guān)閉流(順序不能錯!否則 ZIP 包會損壞,無法解壓)
zipOut.finish(); // 完成 ZIP 寫入
zipOut.close();
servletOut.flush();
servletOut.close();
} catch (Exception e) {
e.printStackTrace();
// 實(shí)際項(xiàng)目中建議自定義異常處理,給前端返回明確的錯誤提示
response.setStatus(500);
}
}
}
六、關(guān)鍵注意事項(xiàng)(避坑重點(diǎn))
這部分一定要仔細(xì)看!很多新手實(shí)現(xiàn)后,出現(xiàn) ZIP 包損壞、中文亂碼、數(shù)據(jù)缺失等問題,大多是因?yàn)楹雎粤诉@些細(xì)節(jié)。
- 流的關(guān)閉順序:必須先執(zhí)行 zipOut.closeArchiveEntry()(關(guān)閉當(dāng)前 ZIP 條目),再執(zhí)行 zipOut.finish(),最后關(guān)閉 ZIP 流和響應(yīng)流。順序顛倒會導(dǎo)致 ZIP 包損壞,無法解壓。
- 中文文件名亂碼:通過 URLEncoder.encode(zipFileName, "UTF-8") 對 ZIP 文件名和 Excel 文件名編碼,兼容 Chrome、Firefox、Edge 等所有主流瀏覽器,避免中文亂碼。
- Excel 寫入方式:示例中使用 ByteArrayOutputStream 臨時存儲 Excel 內(nèi)容,屬于內(nèi)存級寫入,不占用磁盤空間,性能更高。不建議直接寫入磁盤文件再打包,會增加磁盤 IO 開銷。
- 異常處理:實(shí)際項(xiàng)目中,建議替換 try-catch 中的 printStackTrace(),使用全局異常處理器,給前端返回明確的錯誤信息(如“導(dǎo)出失敗,請重試”),提升用戶體驗(yàn)。
七、擴(kuò)展:導(dǎo)出更多Excel文件
如果需要導(dǎo)出 3 個、4 個甚至更多 Excel 文件,無需修改工具類,只需在接口中繼續(xù)調(diào)用 ExcelZipExportUtil.writeExcelToZip() 方法即可。
示例(新增“商品列表”Excel):
// 新增商品列表數(shù)據(jù)(假設(shè)已有 GoodsData 實(shí)體類) List<GoodsData> goodsList = new ArrayList<>(); // ... 構(gòu)造商品數(shù)據(jù) // 新增一個 Excel 寫入 ZIP 流 ExcelZipExportUtil.writeExcelToZip(zipOut, "商品列表.xlsx", goodsList, GoodsData.class);
八、測試效果驗(yàn)證
代碼寫完后,啟動 Spring Boot 項(xiàng)目,通過以下步驟測試,確保導(dǎo)出正常:
- 訪問接口地址:
http://localhost:8080/export/twoExcel(端口號根據(jù)自己項(xiàng)目配置調(diào)整); - 瀏覽器會自動彈出下載提示,下載的文件名為“用戶訂單數(shù)據(jù).zip”;
- 解壓 ZIP 壓縮包,會得到兩個 Excel 文件:用戶列表.xlsx 和 訂單列表.xlsx;
- 打開 Excel 文件,檢查表頭和數(shù)據(jù)是否正常(和接口中構(gòu)造的測試數(shù)據(jù)一致)。
九、總結(jié)
代碼通用可復(fù)用,總結(jié)下來就是 3 步:
- 引入 EasyExcel 和 ZIP 依賴,做好前置準(zhǔn)備;
- 定義 Excel 對應(yīng)的實(shí)體類,封裝通用工具類(處理 ZIP 打包和響應(yīng)頭);
- 在接口中構(gòu)造數(shù)據(jù),調(diào)用工具類將多個 Excel 寫入 ZIP 流,返回給前端。
該方案適用于所有 Spring Boot 項(xiàng)目,支持任意數(shù)量的 Excel 導(dǎo)出,避開了新手常踩的坑(流順序、中文亂碼),直接復(fù)制代碼就能落地使用。如果你的項(xiàng)目中有類似需求,不妨試試這個方案~
到此這篇關(guān)于Java+EasyExcel實(shí)現(xiàn)單個接口導(dǎo)出多個Excel的示例詳解的文章就介紹到這了,更多相關(guān)Java EasyExcel導(dǎo)出多個Excel內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot應(yīng)用中Mybatis輸出SQL日志的3種方法代碼示例
在前臺請求數(shù)據(jù)的時候,sql語句一直都是打印到控制臺的,有一個想法就是想讓它打印到日志里,該如何做呢?這篇文章主要給大家介紹了關(guān)于Springboot應(yīng)用中Mybatis輸出SQL日志的3種方法,需要的朋友可以參考下2024-01-01
Java?實(shí)戰(zhàn)項(xiàng)目之學(xué)生信息管理系統(tǒng)的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11
Spring boot中自定義Json參數(shù)解析器的方法
這篇文章主要介紹了Spring boot中自定義Json參數(shù)解析器的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01
JDBC以反射機(jī)制加載類注冊驅(qū)動連接MySQL
這篇文章介紹了JDBC以反射機(jī)制加載類注冊驅(qū)動連接MySQL的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-01-01

