使用MongoDB GridFS分段下载视频时遇FileNotFound错误求助
问题分析与解决方案
核心问题:openDownloadStream参数错误
你用文件名作为openDownloadStream的第一个参数,但GridFS的openDownloadStream要求第一个参数是文件的**_id(ObjectId类型)**,不是文件名。虽然你通过filename查到了fileInfo,但打开流时传错了标识,导致GridFS找不到对应文件,抛出FileNotFound错误。
其他潜在问题
- 同步
for循环里创建多个异步流,会导致所有流同时启动,不仅效率低,还会出现回调顺序混乱的问题,最后拼接的chunk可能顺序错误。 - 手动收集chunk再拼接的方式完全没必要,GridFS下载流本身支持直接通过
start和end参数指定分段,而且可以直接把流管道到响应对象,不用手动存Buffer。 - 错误处理不完整,流的错误没有正确终止HTTP响应,会导致请求挂起。
修正后的代码示例
方案1:正确使用_id打开流,优化下载逻辑
const { ObjectId } = require('mongodb'); const fs = require('fs'); app.get('/download-video', async (req, res) => { if (db === null) { console.error('Error connecting to MongoDB'); return res.status(500).send('Error connecting to MongoDB'); } const filename = '514a65328a1e0fef0c936c1109bbc946.mp4'; // 先通过文件名查文件信息 const fileInfo = await db.collection('fs.files').findOne({ filename }); if (!fileInfo) { console.error('File not found'); return res.status(404).json({ message: "file not found" }); } const bucket = new mongodb.GridFSBucket(db); // 关键:用fileInfo._id作为openDownloadStream的参数 const downloadStream = bucket.openDownloadStream(new ObjectId(fileInfo._id)); // 设置响应头,直接把流管道到响应 res.setHeader('Content-Type', 'video/mp4'); res.setHeader('Content-Length', fileInfo.length); downloadStream.pipe(res); downloadStream.on('error', (error) => { console.error('Download error:', error); res.status(500).json({ message: error.message }); }); downloadStream.on('end', () => { console.log('Video download completed'); }); });
方案2:支持HTTP Range请求(视频流必备)
如果是做视频流服务器,必须支持客户端的Range请求,让浏览器可以分段加载视频,这是GridFS的标准用法:
app.get('/stream-video', async (req, res) => { if (db === null) { console.error('Error connecting to MongoDB'); return res.status(500).send('Error connecting to MongoDB'); } const filename = '514a65328a1e0fef0c936c1109bbc946.mp4'; const fileInfo = await db.collection('fs.files').findOne({ filename }); if (!fileInfo) { return res.status(404).json({ message: "file not found" }); } const fileLength = fileInfo.length; const range = req.headers.range; if (range) { // 解析Range头,比如 bytes=0-1023 const parts = range.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileLength - 1; const chunksize = (end - start) + 1; const bucket = new mongodb.GridFSBucket(db); const downloadStream = bucket.openDownloadStream(new ObjectId(fileInfo._id), { start, end }); res.writeHead(206, { 'Content-Range': `bytes ${start}-${end}/${fileLength}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', }); downloadStream.pipe(res); } else { // 没有Range请求,返回完整文件 res.writeHead(200, { 'Content-Length': fileLength, 'Content-Type': 'video/mp4', }); const bucket = new mongodb.GridFSBucket(db); bucket.openDownloadStream(new ObjectId(fileInfo._id)).pipe(res); } });
关键修正点总结
- 永远用文件的
_id(ObjectId类型)调用openDownloadStream,不要用文件名。 - 利用Node.js的流管道机制,避免手动收集Buffer,性能更高且代码更简洁。
- 视频流场景必须支持HTTP Range请求,这是浏览器原生支持视频分段加载的标准方式。
内容的提问来源于stack exchange,提问作者root
相关产品推荐
相关产品推荐

