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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 06:27:25