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

Node.js如何向用户发送文件?Heroku部署后下载功能失效

问题核心原因

你本地所谓的“正常运行”是假象,现有代码从响应逻辑到部署适配全有问题,和ytdl-core本身无关:

  • 你根本没把下载的文件返回给用户:代码里用fs.createWriteStream把视频流写到了服务端运行目录的本地磁盘里,写完就直接res.redirect("/")跳回首页,响应早就结束了。本地测试时你自己开着服务、能直接进项目文件夹看到生成的video.mp4,就误以为功能正常,但所有访问网站的客户端根本收不到任何文件数据。
  • Heroku的文件系统是临时易失的:就算你想先把文件存到服务端再返回给用户,Heroku的dyno实例每次重启、部署、扩缩容时,所有写入本地磁盘的文件都会被直接清空,且不同实例之间文件不互通,本地存文件的逻辑在Heroku上完全走不通。
  • 参数获取逻辑太脆弱:靠Object.keys(req.body)[1]取选项、靠address.slice(32)切视频ID的写法,只要链接是短链、带追踪参数、是移动端域名,或者表单提交的字段顺序变了,直接就会取错值导致下载失败。
修复方案

核心思路是跳过服务端本地存文件的步骤,直接把ytdl拉取的视频/音频流透传给客户端响应,同时设置正确的下载响应头,具体操作:

  • 调整接口逻辑,移除fs.createWriteStream写本地文件的代码,直接将ytdl生成的流通过pipe传给响应对象res
  • 给响应设置正确的Content-Disposition头,告诉浏览器这是需要下载的附件,而不是普通网页内容
  • 替换硬编码切URL、靠字段顺序取参数的逻辑,用ytdl自带的方法校验、解析视频链接,表单明确传option字段
  • 加基础错误捕获,避免链接无效、视频受限的时候服务直接崩溃

修复后的可直接用代码

const ytdl = require('ytdl-core');
// 移除fs相关的本地写文件逻辑,不需要在服务端落地存储文件

app.post("/download", async (req, res) => {
  try {
    // 直接从body取明确传的字段,不要靠Object.keys顺序取
    const { searchBar, option } = req.body;

    // 校验YouTube链接合法性
    if (!ytdl.validateURL(searchBar)) {
      return res.status(400).send("请输入有效的YouTube视频链接");
    }

    // 根据选择的类型配置ytdl参数
    const ytdlOptions = option === "audio"
      ? { filter: "audioonly", quality: "highestaudio" }
      : { filter: "videoandaudio", quality: "highest" };

    // 获取视频信息生成合法文件名,过滤掉文件名不支持的特殊字符
    const videoInfo = await ytdl.getBasicInfo(searchBar);
    const safeTitle = videoInfo.videoDetails.title.replace(/[<>:"/\\|?*]/g, "");
    const fileExt = option === "audio" ? "mp3" : "mp4";
    const fileName = `${safeTitle}.${fileExt}`;

    // 设置下载响应头
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="${encodeURIComponent(fileName)}"`
    );
    res.setHeader(
      "Content-Type",
      option === "audio" ? "audio/mpeg" : "video/mp4"
    );

    // 直接将ytdl流转发给响应,不落地到服务端磁盘
    ytdl(searchBar, ytdlOptions).pipe(res);
  } catch (err) {
    console.error("下载流程出错:", err);
    res.status(500).send("下载失败,请检查链接是否可访问,或稍后重试");
  }
});

配套调整项

  • 前端表单的两个单选按钮要设置统一的name="option"属性,对应value分别为video和audio,不要依赖请求体的字段顺序传参
  • Heroku默认的请求超时时间是30秒,下载时长超过30秒的长视频可能会被平台截断,给老人日常用的普通短视频不会有问题,如果需要支持长视频可以考虑调整流的分片逻辑,或者更换无严格请求超时限制的部署平台
  • 不要尝试在Heroku上用本地文件缓存视频,除了文件系统会被清空之外,写入的临时文件还会占用dyno内存配额,容易触发内存超限导致服务重启

内容的提问来源于stack exchange,提问作者Stigma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:18:49