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
相关产品推荐
相关产品推荐

