基于SpringBoot實現(xiàn)多文件批量下載并打包為ZIP壓縮包的完整解決方案
一、功能需求分析
我們要實現(xiàn)的核心功能:
- 前端傳入多個文件 ID,后端根據 ID 查詢文件的存儲路徑
- 將這些文件讀取并打包成一個 ZIP 壓縮包
- 通過 HTTP 響應將 ZIP 包直接下載到客戶端
- 處理文件不存在、IO 異常等邊界情況
二、核心技術點
- SpringBoot Web:提供 HTTP 接口、處理請求響應
- Java IO/NIO:讀取本地文件、操作輸出流
- java.util.zip:JDK 原生 ZIP 壓縮工具類,無需引入額外依賴
- 跨域處理:解決前后端分離架構下的跨域問題
- 日志記錄:記錄下載操作日志(可選)
三、代碼實現(xiàn)
3.1 實體類:Attachment(文件附件實體)
首先定義文件附件實體類,對應數(shù)據庫中存儲的文件信息:
package com.itl.project.common.attachment.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.itl.framework.aspectj.lang.annotation.Excel;
import com.itl.framework.web.domain.BaseEntity;
import java.util.Date;
/**
* 文件信息對象 sys_attachment
*
* @author itl
*/
@Data
public class Attachment extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 文件主鍵 */
private Long fileId;
/** 文件業(yè)務id */
@Excel(name = "文件業(yè)務id")
@TableField(exist = false)
private String fileBusinessId;
/** 文件key */
@Excel(name = "文件key")
private String fileKey;
/** 文件名稱 */
@Excel(name = "文件名稱")
private String fileName;
/** mime類型 */
@Excel(name = "mime類型")
private String mimeType;
/** 文件大小 */
@Excel(name = "文件大小")
private Long fileSize;
/** 文件路徑 */
@Excel(name = "文件路徑")
private String filePath;
/** 文件地址 */
@Excel(name = "文件地址")
private String fileUrl;
/** 文件后綴 */
@Excel(name = "文件后綴")
private String suffix;
/** 是否存在 */
private boolean exists = false;
/** 創(chuàng)建時間 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 版本號
*/
private String version;
/**
* 文件備注
*/
private String comment;
@TableField(exist = false)
private String fileIds;
}
3.2 核心工具類:CompressDownloadUtil(ZIP 壓縮工具)
封裝 ZIP 壓縮的核心邏輯,負責將多個文件打包到輸出流中:
package com.itl.common.utils;
import com.itl.project.common.attachment.domain.Attachment;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 文件壓縮下載工具類
* 功能:將多個文件壓縮到指定輸出流中,用于ZIP包下載
*/
public class CompressDownloadUtil {
/**
* 將多個文件壓縮到指定輸出流中
*
* @param lists 需要壓縮的文件列表
* @param outputStream 壓縮后的輸出流(直接關聯(lián)HTTP響應輸出流)
*/
public static void compressZip(List<Attachment> lists, OutputStream outputStream) {
ZipOutputStream zipOutStream = null;
try {
// 包裝成ZIP格式輸出流,提升寫入效率
zipOutStream = new ZipOutputStream(new BufferedOutputStream(outputStream));
// 設置壓縮方法為DEFLATED(默認,有壓縮效果)
zipOutStream.setMethod(ZipOutputStream.DEFLATED);
// 循環(huán)處理每個文件
for (Attachment file : lists) {
java.io.File localFile = new java.io.File(file.getFilePath());
// 校驗文件是否存在,避免空指針或文件找不到異常
if (localFile.exists() && localFile.isFile()) {
try (FileInputStream fileInputStream = new FileInputStream(localFile)) {
// 讀取文件字節(jié)數(shù)據
byte[] data = new byte[(int) localFile.length()];
fileInputStream.read(data);
// 創(chuàng)建ZIP條目(壓縮包內的文件名稱)
ZipEntry zipEntry = new ZipEntry(file.getFileName());
zipEntry.setSize(localFile.length());
// 將條目寫入ZIP流
zipOutStream.putNextEntry(zipEntry);
zipOutStream.write(data);
// 關閉當前條目
zipOutStream.closeEntry();
}
}
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("文件壓縮失?。? + e.getMessage());
} finally {
// 關閉流(注意關閉順序:先關ZIP流,再關基礎輸出流)
try {
if (Objects.nonNull(zipOutStream)) {
zipOutStream.flush();
zipOutStream.close();
}
if (Objects.nonNull(outputStream)) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.3 控制器:文件下載接口
import com.itl.common.utils.CompressDownloadUtil;
import com.itl.project.common.attachment.domain.Attachment;
import com.itl.project.common.attachment.service.AttachmentService;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.UUID;
/**
* 文件下載控制器
*/
@RestController
@RequestMapping("/file")
public class FileDownloadController {
// 注入文件附件服務(實際項目中通過@Autowired注入)
private AttachmentService attachmentService;
/**
* 多文件下載為ZIP壓縮包
* @param fileid 多個文件ID,建議用逗號分隔(如:1,2,3)
* @param request HTTP請求
* @param response HTTP響應
* @throws UnsupportedEncodingException 編碼異常
*/
@Log(title = "多文件下載", businessType = BusinessType.DOWNLOAD)
@CrossOrigin // 解決跨域問題(前后端分離必加)
@GetMapping("/downloadZip/{fileid}")
public void downloadZip(@PathVariable("fileid") String fileid,
HttpServletRequest request,
HttpServletResponse response) throws UnsupportedEncodingException {
// 1. 根據文件ID查詢文件列表
Attachment attachment = new Attachment();
attachment.setFileIds(fileid);
List<Attachment> attachmentList = attachmentService.selectAttachmentList(attachment);
// 2. 校驗文件列表是否為空
if (attachmentList.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 3. 設置響應頭,告訴瀏覽器下載文件
// 生成唯一的ZIP包名稱(避免重名)
String downloadName = UUID.randomUUID().toString().replaceAll("-", "") + ".zip";
// 解決文件名中文亂碼問題
String fileName = new String(downloadName.getBytes("UTF-8"), "ISO8859-1");
// 設置響應頭
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
// 設置響應內容類型為二進制流(通用文件下載類型)
response.setContentType("application/octet-stream");
// 禁用緩存
response.setHeader("Cache-Control", "no-cache");
// 4. 調用工具類壓縮文件并寫入響應輸出流
try {
CompressDownloadUtil.compressZip(attachmentList, response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
四、關鍵細節(jié)說明
4.1 解決文件名中文亂碼
String fileName = new String(downloadName.getBytes("UTF-8"), "ISO8859-1");
4.2 流的關閉順序
在工具類中,關閉流的順序必須是:先關閉 ZipOutputStream,再關閉基礎的 OutputStream。因為 ZipOutputStream 是包裝流,關閉包裝流時會自動刷新緩沖區(qū),確保所有數(shù)據都寫入基礎流。
4.3 異常處理
- 校驗文件是否存在:避免讀取不存在的文件導致 IO 異常
- 響應狀態(tài)碼:文件不存在返回 404,服務器異常返回 500,前端可根據狀態(tài)碼給出友好提示
- try-with-resources:在讀取文件時使用try (FileInputStream fileInputStream = new FileInputStream(localFile)),自動關閉流,簡化代碼
4.4 跨域處理
- @CrossOrigin注解解決前后端分離架構下的跨域問題,如果是全局跨域配置,可省略該注解。
五、前端調用示例(Axios)
// 前端下載ZIP包示例
function downloadZip(fileIds) {
// 創(chuàng)建a標簽,通過GET請求下載
const a = document.createElement('a');
a.href = `/file/downloadZip/${fileIds}`;
a.download = '文件包.zip'; // 可選,優(yōu)先使用后端返回的文件名
a.click();
// 移除a標簽
a.remove();
}
// 調用示例:下載ID為1,2,3的文件
downloadZip("1,2,3");
六、優(yōu)化建議
- 大文件處理:本文代碼適用于中小文件,若處理大文件,建議使用BufferedInputStream分塊讀取,避免一次性加載文件到內存導致 OOM。
- 文件名去重:如果壓縮包內有重名文件,可在文件名后添加序號(如:test.txt → test (1).txt)。
- 進度條:大文件下載時,可結合 WebSocket 實現(xiàn)下載進度條。
- 權限控制:在接口中添加權限校驗,確保只有授權用戶才能下載文件。
- 日志完善:除了操作日志,可記錄文件下載的大小、耗時、用戶等信息,便于問題排查。
七、總結
本文基于 SpringBoot + JDK 原生 ZIP 工具類,實現(xiàn)了多文件批量下載并打包為 ZIP 壓縮包的功能,核心思路是:
查詢文件列表 → 2. 設置下載響應頭 → 3. 讀取文件并壓縮 → 4. 寫入響應輸出流。
該方案無需引入額外的壓縮依賴,基于 JDK 原生 API 實現(xiàn),輕量且穩(wěn)定,適用于大多數(shù) Web 項目的文件下載場景。如果有更復雜的壓縮需求(如加密、分卷壓縮),可考慮使用 Apache Commons Compress 工具包。
以上就是基于SpringBoot實現(xiàn)多文件批量下載并打包為ZIP壓縮包的完整解決方案的詳細內容,更多關于SpringBoot多文件下載并打包為ZIP壓縮包的資料請關注腳本之家其它相關文章!
相關文章
MyBatis-Plus動態(tài)表名使用selectPage方法不生效問題解析與解決方案
MyBatis-Plus是MyBatis的增強工具,動態(tài)表名是MyBatis-Plus的一個重要功能之一,一些開發(fā)者在使用selectPage方法時可能會遇到動態(tài)表名不生效的問題,本文將深入分析這個問題的原因,并提供相應的解決方案,需要的朋友可以參考下2023-12-12
Spring MVC如何使用@RequestParam注解獲取參數(shù)
這篇文章主要介紹了Spring MVC實現(xiàn)使用@RequestParam注解獲取參數(shù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
Spring JPA聯(lián)表查詢之OneToMany源碼解析
這篇文章主要為大家介紹了Spring JPA聯(lián)表查詢之OneToMany源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04

