Express开发中如何直接上传图片至Cloudinary且不存储至本地目录
不生成本地副本的直传实现方案
当前流程会在本地生成文件,本质是因为Multer配置了磁盘存储,默认会把上传的文件先完整写到服务器的public/uploads目录,再让后续代码读取这个本地文件传给Cloudinary。只要把Multer的存储逻辑改成内存存储,就能让上传的文件只存在于服务器内存中,完全不会写本地磁盘,改造逻辑不复杂,适配所有生产部署环境,具体改动点如下:
1. 替换Multer的存储配置
把原来的磁盘存储改成内存存储,同时补上文件大小限制,避免超大文件占满内存:
const multer = require("multer"); // 内存存储:文件直接缓存在内存,不会写入本地磁盘 const storage = multer.memoryStorage(); const fileFilter = (req, file, cb) => { if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") { cb(null, true); } else { cb({ message: "Unsupported file format" }, false); } }; const upload = multer({ storage: storage, fileFilter: fileFilter, // 限制单张图片最大2MB,足够dashboard场景使用 limits: { fileSize: 2 * 1024 * 1024 } }); module.exports = upload;
改完之后Multer会自动把文件内容存在req.file.buffer里,全程不会生成本地文件路径。
2. 改造Cloudinary上传工具
Cloudinary的SDK本身支持直接接收内存中的文件Buffer,不需要依赖本地文件路径,这里顺便修正原来Promise错误捕获的问题——之前的代码里出错直接throw,不会被Promise链正常捕获:
const cloudinary = require("../config/cloudinary"); exports.uploads = (fileBuffer, folder) => { return new Promise((resolve, reject) => { // 直接创建上传流,接收内存中的文件Buffer const uploadStream = cloudinary.uploader.upload_stream( { resource_type: "auto", folder: folder, }, (err, result) => { if (err) return reject(err); resolve({ url: result.url, public_id: result.public_id, }); } ); // 把内存中的文件内容写入上传流 uploadStream.end(fileBuffer); }); };
3. 调整控制器的传参逻辑
原来传给上传函数的是本地文件路径req.file.path,现在没有本地文件了,直接把内存里的Buffer传进去就行,同时补上文件存在的校验,避免空文件报错:
const SectionType = require("../models/section-type-model"); const cloudinaryUploader = require("../utils/cloudinaryUploader"); const createSectionType = async (req, res) => { const body = req.body; // 补上必填校验:标题、描述、图片缺一不可 if (!body?.title || !body?.description || !req.file) { return res.status(400).json({ success: false, error: "标题、描述、图片为必填项", }); } try { const uploadResult = await cloudinaryUploader.uploads(req.file.buffer, "Section-Types"); const sectionType = new SectionType({ title: body.title, description: body.description, image: { url: uploadResult.url, publicId: uploadResult.public_id, }, }); await sectionType.save(); return res.status(201).json({ success: true, id: sectionType._id, message: "Section type created!", }); } catch (error) { return res.status(400).json({ error, message: "Section type not created!", }); } }; module.exports = { createSectionType, };
方案说明
- 改造完成后,整个上传流程不会在服务器本地生成任何图片副本,完全不会占用服务器磁盘空间,哪怕部署在只读文件系统的生产平台也能正常运行
- 去掉了本地磁盘读写的IO环节,整体上传处理速度会比原来的中转流程更快
- 配合之前的文件类型、大小限制,内存占用完全可控,对于dashboard这类单张图片不超过几MB的常规场景,几乎不会有性能压力
如果后续需要支持超过10MB的大文件上传,内存存储的方案就不太适合了,那个场景可以用Cloudinary的客户端直传签名方案,把大文件直接从浏览器传到Cloudinary,完全跳过服务器的文件接收环节,效率更高。
内容的提问来源于stack exchange,提问作者Kishan Bharda
相关产品推荐
相关产品推荐

