**零臨時文件**,直接將多個文件流式壓縮寫入 `HttpServletResponse` 的輸出流,利用 `ZipOutputStream` 邊讀邊壓邊輸出。
@WebServlet("/batchDownload")
public class BatchDownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 獲取前端傳來的文件 ID 列表 (如 "1,2,3")
String[] fileIds = req.getParameterValues("fileId");
if (fileIds == null || fileIds.length == 0) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "未選擇文件");
return;
}
// 2. 設置響應頭:ZIP 下載
String zipName = "download_" + System.currentTimeMillis() + ".zip";
resp.setContentType("application/zip");
resp.setHeader("Content-Disposition", "attachment; filename=\"" + zipName + "\"");
// 3. 核心:ZipOutputStream 包裝 response.getOutputStream()
try (ZipOutputStream zos = new ZipOutputStream(resp.getOutputStream())) {
for (String id : fileIds) {
// 3.1 根據 ID 獲取文件元信息 (路徑、名稱) - 此處模擬
File file = fileService.getFileById(id); // 假設返回 File 對象
if (file == null || !file.exists()) continue;
// 3.2 放入 ZIP 條目
ZipEntry entry = new ZipEntry(file.getName());
zos.putNextEntry(entry);
// 3.3 流式複製文件內容到 ZIP (緩衝區 8KB)
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
}
zos.closeEntry(); // 關閉當前條目
}
zos.finish(); // 完成 ZIP 寫入 (寫入中央目錄)
} // try-with-resources 自動關閉 zos 及底層 outputStream
}
}
| 環節 | 說明 |
|---|---|
| `ZipOutputStream` | 直接寫入 HTTP 響應流,無需在服務器磁盤生成臨時 ZIP 文件,節省 IO、空間、併發風險。 |
| `putNextEntry` / `closeEntry` | 每個文件一個 Entry,必須成對調用,否則 ZIP 結構損壞。 |
| 緩衝區 `byte[8192]` | 避免大文件一次性讀入內存 OOM,流式分塊傳輸。 |
| `try-with-resources` | JDK 7+ 語法,保證 `zos`、`fis` 異常時也能正確關閉,釋放文件句柄。 |
| 文件名編碼 | 中文文件名需 `URLEncoder.encode(name, "UTF-8")` 處理 `Content-Disposition`,防止下載亂碼。 |