如何将formidable上传的文件直接存储到MongoDB GridFS
问题解答
不需要强制先把文件存到public/files本地目录再转存GridFS。你现在的代码之所以会把文件写到本地,是因为手动给formidable指定了业务目录作为上传临时路径,完全可以通过流对接的方式直接把上传文件写入GridFS,省去本地落盘、事后清理临时文件的步骤。
现有代码的可优化点
- 你引入的
gridfs-stream是已经停止维护的第三方旧包,当前MongoDB Node.js驱动原生自带GridFSBucketAPI,不需要额外依赖旧包,兼容性更好。 - 代码里的
File.create()是未定义的无效逻辑,没有实际写入GridFS的作用。 - 将上传临时目录设为对外暴露的
public/files存在安全风险,临时文件本来就不应该能被外部直接访问。 - 每次请求都新建Mongo客户端连接会造成连接泄漏,建议全局复用连接实例。
推荐实现(直接流上传,不落本地业务盘)
通过formidable的文件事件直接把上传流对接GridFS的上传流,全程不会把完整文件写入你的项目目录,性能更好也没有临时文件清理负担:
import formidable from 'formidable'; import { MongoClient, GridFSBucket } from 'mongodb'; export const config = { api: { bodyParser: false, }, }; // 全局复用Mongo客户端,避免连接泄漏 let mongoClient; async function getMongoClient() { if (!mongoClient) { mongoClient = new MongoClient(process.env.MONGODB_URI); await mongoClient.connect(); } return mongoClient; } export default async (req, res) => { const client = await getMongoClient(); const database = client.db('AdStitchr'); // 初始化GridFS桶,对应原有uploads集合 const bucket = new GridFSBucket(database, { bucketName: 'uploads' }); const form = new formidable.IncomingForm(); // 删除原有form.uploadDir = 'public/files'配置,使用系统默认临时目录即可 form.keepExtensions = true; // 核心:监听文件开始上传事件,直接把流导向GridFS form.on('fileBegin', (formName, file) => { // 打开GridFS上传流,可自定义文件名和元数据 const uploadStream = bucket.openUploadStream(file.newFilename, { chunkSizeBytes: 1048576, metadata: { originalName: file.originalFilename, mimetype: file.mimetype } }); // 替换formidable默认的写本地逻辑,直接把上传数据写入GridFS file.writeStream = uploadStream; // 挂载生成的文件ID,方便后续返回 file.gridFsId = uploadStream.id; }); form.parse(req, (err, fields, files) => { if (err) { return res.status(500).send(err.message); } const uploadedFile = files.file; console.log('上传完成,GridFS文件ID:', uploadedFile.gridFsId); // 此处可添加自定义业务逻辑,比如把文件ID关联存储到业务表 res.status(200).json({ status: 'success', fileId: uploadedFile.gridFsId, fileName: uploadedFile.newFilename }); }); };
临时文件转存方案(不推荐)
如果不想调整流逻辑,也可以保留临时文件转存的模式,但需要注意两点:
- 不要把临时目录设到
public/files这类对外可访问的路径,使用formidable默认的系统临时目录即可 - 转存完成后必须手动删除临时文件,避免磁盘空间被占满
核心转存代码片段如下:
import fs from 'fs/promises'; // form.parse回调内逻辑 form.parse(req, (err, fields, files) => { if (err) return res.status(500).send(err.message); const file = files.file; const readStream = fs.createReadStream(file.filepath); const uploadStream = bucket.openUploadStream(file.newFilename); readStream.pipe(uploadStream); uploadStream.on('finish', async () => { // 转存完成删除临时文件 await fs.unlink(file.filepath); res.status(200).json({ status: 'success', fileId: uploadStream.id }); }); uploadStream.on('error', (uploadErr) => { res.status(500).send(uploadErr.message); }); });
这种方案比直接流上传多了一次本地磁盘读写,性能更差,还需要额外处理异常场景下的临时文件清理,优先使用前面的流对接方案。
内容的提问来源于stack exchange,提问作者Russell Harrower
相关产品推荐
相关产品推荐

