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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 02:45:54