Node.js+Express与React+Axios实现文件下载报ERR_STREAM_CANNOT_PIPE错误如何解决
解决方案
一、后端错误修复
错误原因
你触发ERR_STREAM_CANNOT_PIPE的核心问题是流的传输方向完全写反:
- Express的
res是写给前端的可写流,不能作为可读流调用pipe方法输出到本地文件流 - 正确逻辑是读取本地待下载文件的可读流,将其pipe到
res对象传输给前端
修正后的后端代码
import express from "express"; import { verifyJWT } from "../services/tokens/verifyJWT.js"; import Training from "../models/Training.js"; import fs from "fs"; import path from "path"; // 建议用path拼接路径,避免兼容问题 const basePath = "/home/marc/Disco/Projects/AtlasFitness/"; const dirname = "uploads/trainings/"; routerTrainer.post("/download-training", verifyJWT, async (req, res) => { const { trainer_id, training_id } = req.body; const training = await Training.findOne({ where: { id: training_id, trainer_id }, }); if (!training) { return res.status(404).send("对应训练文件不存在"); } const filePath = path.join(basePath, dirname, training.file_id); // 先判断文件是否存在 if (!fs.existsSync(filePath)) { return res.status(404).send("文件已被删除"); } // 设置下载响应头 res.setHeader("Content-Type", "application/octet-stream"); // 这里的filename可以自定义下载显示的文件名,注意编码处理避免中文乱码 res.setHeader("Content-Disposition", `attachment; filename=${path.basename(filePath)}`); // 如果有跨域需求,需要暴露这个头让前端能拿到文件名 res.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); // 创建可读流pipe给响应对象 const fileStream = fs.createReadStream(filePath); fileStream.pipe(res); // 流传输完成自动结束响应 fileStream.on("finish", () => { res.end(); console.log("文件传输完成"); }); // 错误处理 fileStream.on("error", (err) => { res.status(500).send("文件读取失败"); }); })
二、前端自动下载实现
代码修改点
- 给Axios请求添加
responseType: 'blob'配置,指定响应数据以二进制blob格式接收 - 拿到响应后创建临时下载链接,触发浏览器自带的下载行为
修正后的前端代码
const downloadTraining = async (id) => { const JWT = new ClassJWT(); const axiosReq = axios.create(); await JWT.checkJWT(); axiosReq .post(`${serverPath}/download-training`, { trainer_id: await JWT.getId(), training_id: id, token: JWT.getToken(), }, { responseType: 'blob' // 必须加这个配置 }) .then((res) => { // 从响应头提取文件名(如果需要后端返回的文件名) const contentDisposition = res.headers['content-disposition']; const fileName = contentDisposition ? decodeURIComponent(contentDisposition.match(/filename=(.*)/)[1]) : `training_${id}`; // 创建blob对象 const blob = new Blob([res.data]); // 创建临时下载链接 const downloadUrl = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = downloadUrl; a.download = fileName; // 触发点击下载 document.body.appendChild(a); a.click(); // 清理临时资源 document.body.removeChild(a); window.URL.revokeObjectURL(downloadUrl); }) .catch((err) => console.log(err)); }
内容的提问来源于stack exchange,提问作者Gonsa02
相关产品推荐
相关产品推荐

