本文详解 react 调用 node.js 接口生成并下载 zip 文件时常见的“损坏归档”问题,核心在于服务端未正确处理 archiver 流的生命周期与响应时机,需避免写入本地文件再读取,而应直接将压缩流管道至 http 响应。
在前后端分离架构中,通过 POST 请求触发服务端 ZIP 打包并下载是一个高频需求。但如案例所示,尽管服务端本地生成的 ex.zip 文件完整可用,前端下载的却总是损坏(如报错“invalid zip file”或解压失败),根本原因在于:Node.js 服务端错误地混合了文件写入与 HTTP 响应逻辑,且未等待压缩流完成就提前结束响应。
原服务端代码存在三处关键问题:
服务端应跳过本地文件存储,直接将 archiver 流管道(.pipe())到 Express 的 res 对象,并设置正确的响应头:
const archiver = require("archiver");
const express = require("express");
const cors = require("cors");
const app = express();
app.use(cors());
app.post("/download", (req, res) => {
// 设置响应头:告知浏览器这是附件,且指定文件名
res.attachment("ex_new.zip");
// 创建 ZIP 归档实例
const archive = archiver("zip", {
zlib: { level: 9 } // 可选:启用最高压缩率
});
// 捕获归档错误(如路径不存在)
archive.on("error", (err) => {
console.error("Archiver error:", err);
res.status(500).send({ error: "Failed to generate archive" });
});
// 监听归档完成事件,确保流彻底结束
archive.on("end", () => {
console.log("Archive written successfully.");
});
// 将归档流直接管道至 HTTP 响应
archive.pipe(res);
// 添加目录(确保 'output' 目录存在且有读取权限)
archive.directory("output", false); // false 表示不包含外层目录名
// 显式触发归档结束(必须调用)
archive.finalize();
});
app.listen(5000, () => console.log("Server started on http://localhost:5000"));您现有的 React fetch + blob + URL.createObjectURL 方案完全正确,无需修改:
function test() {
fetch("http://localhost:5000/download", {
method: "POST",
})
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.blob();
})
.then((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.dow
nload = "ex_new.zip";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
})
.catch((err) => console.error("Download failed:", err));
}遵循上述方案,即可实现高效、可靠、零临时文件的 ZIP 下载,彻底解决“前端下载损坏归档”的顽疾。