Express使用Multer上传文件前如何统计请求接收的文件数量
问题说明
基于Node.js + Express + Multer实现个人主页图片上传接口,业务规则限制单个用户最多存储5张图片,需要在文件正式写入磁盘前,先统计本次请求携带的文件数量,结合数据库查询到的用户已上传图片数校验上传额度,超额时直接返回剩余可上传数量提示,额度充足时再执行文件存储逻辑。
原有实现的核心矛盾是:如果直接将Multer的diskStorage实例作为路由中间件,Multer会先把所有文件写入磁盘再进入路由业务逻辑,哪怕后续校验出超额,文件已经落盘会产生无效垃圾文件;如果在路由处理函数里先查库,又因为Multer还没解析请求,拿不到本次上传的文件数。
实现方案
核心思路是拆分Multer的解析和存储步骤:先用内存存储模式解析请求拿到文件列表(这个阶段不会写磁盘),完成业务校验后再手动将文件写入目标目录。
第一步:调整Multer配置
const multer = require('multer'); const fs = require('fs/promises'); const path = require('path'); // 服务启动时提前创建存储目录,避免写文件报错 (async () => { try { await fs.access('images'); } catch { await fs.mkdir('images', { recursive: true }); } })(); // 临时解析配置:用内存存储暂存文件,不直接写磁盘 const tempUpload = multer({ storage: multer.memoryStorage(), fileFilter: (req, file, cb) => { // 注意:jpg格式标准MIME类型是image/jpeg,原配置里的image/jpg是无效判断 if (file.mimetype !== 'image/jpeg') { return cb(new Error("仅支持上传.jpg格式图片")); } cb(null, true); }, limits: { files: 4 // 保持原配置的单次上传数量上限 } }).array('images');
第二步:调整路由处理逻辑
router.post('/', validateToken, (req, res, next) => { // 先解析请求中的文件,暂存在内存 tempUpload(req, res, async (parseErr) => { if (parseErr) return res.status(400).send(parseErr.message); try { // 这里可以直接拿到本次上传的文件总数 const uploadCount = req.files?.length || 0; if (uploadCount === 0) { return res.status(400).send("请选择要上传的图片"); } // 查询用户已上传图片数量 const userImgList = await queryPromise( "SELECT id FROM images WHERE uid = ?", [req.user.uid] // 从validateToken解析的登录态中取当前用户ID,不要硬编码 ); const existCount = userImgList.length; const MAX_TOTAL = 5; const remain = MAX_TOTAL - existCount; // 额度校验 if (uploadCount > remain) { return res.status(400).send(`您最多还能上传${remain}张图片`); } // 校验通过,将内存中的文件写入磁盘 const saveTasks = req.files.map(async (file) => { const fileName = `${Date.now()}_${Math.random().toString(36).slice(2, 10)}.jpg`; const savePath = path.join('images', fileName); await fs.writeFile(savePath, file.buffer); // 写入文件记录到数据库 await queryPromise( "INSERT INTO images (uid, file_path) VALUES (?, ?)", [req.user.uid, savePath] ); return fileName; }); const uploadedList = await Promise.all(saveTasks); res.send({ msg: "上传成功", data: uploadedList }); } catch (err) { next(err); } }); });
注意事项
- 内存存储模式下,文件数据临时存在请求进程的内存中,校验不通过时会随请求结束被垃圾回收,不会在磁盘残留无效文件,对于单张体积几MB的普通图片场景完全够用。
- 如果需要支持超大体积文件上传,可以自定义Multer存储引擎,在文件写入逻辑中先挂起操作,等待额度校验完成后再决定是写入文件还是终止上传,避免占用过多内存。
- 数据库查询记得用参数化语句,不要直接拼接用户ID,避免SQL注入风险。
内容的提问来源于stack exchange,提问作者arrmani88
相关产品推荐
相关产品推荐

