如何使用Express为MongoDB GridFsBucket文件生成直接下载链接
解决方案
问题原因
res.download方法仅支持传入本地文件系统路径,无法直接接收Buffer或流对象,你的场景不需要使用该方法,手动设置响应头即可触发浏览器下载行为。gridfsbucket.openDownloadStream()返回的是Node.js可读流实例,不能直接通过Buffer.from转换,必须等待流完整读取完成才能生成Buffer,更推荐直接用流管道传输避免全量加载文件到内存。
方案1:流直接管道传输(推荐)
该方案完全不需要临时存储文件,也不需要将整个文件加载到内存,大文件场景下内存占用极低,符合你的需求,代码如下:
const { ObjectId } = require('mongodb'); const mongoose = require('mongoose'); router.get('/download-file', async (req, res) => { try { const fileId = req.query.fileId; // 校验文件ID合法性 if (!ObjectId.isValid(fileId)) { return res.status(400).send('无效的文件ID'); } const ObjectFileId = new ObjectId(fileId); // 初始化GridFS桶 const gridfsbucket = new mongoose.mongo.GridFSBucket(mongoose.connection.db, { chunkSizeBytes: 1024, bucketName: 'filesBucket' }); // 查询文件元信息 const files = await gridfsbucket.find({ _id: ObjectFileId }).toArray(); if (!files || files.length === 0) { return res.status(404).send('文件不存在'); } const file = files[0]; // 设置响应头,触发浏览器下载 res.set({ 'Content-Type': file.contentType || 'application/octet-stream', 'Content-Disposition': `attachment; filename="${encodeURIComponent(file.filename)}"`, 'Content-Length': file.length }); // 直接将GridFS下载流管道到响应对象 const downloadStream = gridfsbucket.openDownloadStream(ObjectFileId); downloadStream.pipe(res); // 处理流异常 downloadStream.on('error', (err) => { console.error('下载流错误:', err); res.status(500).send('文件下载失败'); }); } catch (err) { console.error('下载接口错误:', err); res.status(500).send('服务器内部错误'); } });
方案2:Buffer返回方案(适合需要预处理文件内容的场景)
如果你确实需要先拿到完整文件Buffer做内容处理,可以按如下方式修改代码:
封装的文件下载方法修改
// Node v15+ 内置stream/promises,低版本可使用concat-stream第三方包实现相同功能 const { buffer } = require('stream/promises'); async function downloadFileFromDB(fileId) { const gridfsbucket = new mongoose.mongo.GridFSBucket(mongoose.connection.db, { chunkSizeBytes: 1024, bucketName: 'filesBucket' }); try { const stream = gridfsbucket.openDownloadStream(fileId); // 等待流读取完成转为Buffer const fileBuffer = await buffer(stream); // 查询文件元信息返回 const files = await gridfsbucket.find({ _id: fileId }).toArray(); return { buffer: fileBuffer, filename: files[0]?.filename || 'unknown', contentType: files[0]?.contentType || 'application/octet-stream' }; } catch (err) { console.error('读取文件出错:', err); throw err; } }
路由代码修改
router.get('/download-file', async (req,res) => { try { const fileId = req.query.fileId; if (!ObjectId.isValid(fileId)) { return res.status(400).send('无效的文件ID'); } const ObjectFileId = new ObjectId(fileId); const fileData = await fileFacade.downloadFileFromDB(ObjectFileId); // 设置响应头后返回Buffer res.set({ 'Content-Type': fileData.contentType, 'Content-Disposition': `attachment; filename="${encodeURIComponent(fileData.filename)}"`, 'Content-Length': fileData.buffer.length }); res.send(fileData.buffer); } catch (err) { console.error('下载出错:', err); res.status(500).send('文件下载失败'); } })
内容的提问来源于stack exchange,提问作者tchris
相关产品推荐
相关产品推荐

