You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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("文件读取失败");
  });
})

二、前端自动下载实现

代码修改点

  1. 给Axios请求添加responseType: 'blob'配置,指定响应数据以二进制blob格式接收
  2. 拿到响应后创建临时下载链接,触发浏览器自带的下载行为

修正后的前端代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 17:45:03