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

Node.js(Express)中如何将req.body参数传入multer上传中间件?

解决方案

一、关于req.body先空后可读取的原因

你发送的是multipart/form-data类型的请求,body-parser中间件无法解析这种格式的表单数据。只有当multer执行文件上传逻辑时,才会同时解析表单中的文本字段并填充到req.body中。因此在调用upload(req, res)之前,req.body是空的;调用完成后,multer完成了解析,req.body就能正常读取参数。

二、动态生成上传文件夹路径的实现

下面提供两种可行方案,推荐使用方案一:

方案一:直接从req.body读取参数(推荐)

修改upload.js中的destination回调,直接从req.body获取selfGovernmentName和week参数,同时增加参数校验避免无效路径:

const util = require("util");
const path = require("path");
const multer = require("multer");
const moment = require("moment");
const slugify = require("slugify");
const fs = require("fs");

const maxSize = 500000;

var storage = multer.diskStorage({
  destination: (req, file, callback) => {
    // 从解析后的req.body中获取参数
    const selfGovernment = req.body.selfGovernmentName;
    const week = req.body.week;

    // 参数校验,避免空值导致路径错误
    if (!selfGovernment || !week) {
      return callback("缺少selfGovernmentName或week参数", null);
    }

    const dir1 = path.join(`${__dirname}/../../uploads/${selfGovernment}`);
    const dir2 = path.join(`${__dirname}/../../uploads/${selfGovernment}/${week}`);

    // 递归创建文件夹(无需手动判断是否存在)
    fs.mkdirSync(dir2, { recursive: true });

    callback(null, dir2);
  },
  filename: (req, file, callback) => {
    const fileSize = parseInt(req.headers["content-length"]);
    const allowedTypes = ["image/png", "image/jpeg", "image/jpg", "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"];

    if (!allowedTypes.includes(file.mimetype)) {
      const message = `${file.originalname} je chybný súbor. Odosielajte iba png/jpeg/jpg alebo pdf/doc.`;
      return callback(message, null);
    }
    
    if (fileSize > maxSize) {
      const tooLargeMessage = `${file.originalname} je chybný súbor. Odosielajte súbory do 5MB.`;
      return callback(tooLargeMessage, null);
    }  

    const formatedDate = moment(Date.now()).format("DD-MM-YYYY_HH-mm-ss");
    const newName = slugify(file.originalname);
    const filename = `${formatedDate}_${newName}`;
    callback(null, filename);
  }
});

var uploadFiles = multer({ storage: storage, limits: { fileSize: maxSize } }).array("multi-files", 4);
var uploadFilesMiddleware = util.promisify(uploadFiles);

module.exports = uploadFilesMiddleware;

注意:前端提交表单时,需要先发送selfGovernmentName、week等文本字段,再发送文件字段。因为multipart/form-data是流式解析的,multer会按字段顺序解析,先解析的文本字段会先存入req.body,确保destination回调能读取到参数。

方案二:将上传中间件改为工厂函数(可选)

如果需要更灵活地传递参数,可以将upload.js改为工厂函数,允许控制器传入自定义参数:

修改upload.js:

const util = require("util");
const path = require("path");
const multer = require("multer");
const moment = require("moment");
const slugify = require("slugify");
const fs = require("fs");

const maxSize = 500000;

// 工厂函数,接收文件夹参数并返回上传中间件
function createUploadMiddleware(selfGovernment, week) {
  // 参数校验
  if (!selfGovernment || !week) {
    throw new Error("缺少selfGovernment或week参数");
  }

  const formatedDate = moment(Date.now()).format("DD-MM-YYYY_HH-mm-ss");
  const uploadDir = path.join(`${__dirname}/../../uploads/${selfGovernment}/${week}`);

  // 提前创建文件夹
  fs.mkdirSync(uploadDir, { recursive: true });

  const storage = multer.diskStorage({
    destination: (req, file, callback) => {
      callback(null, uploadDir);
    },
    filename: (req, file, callback) => {
      const fileSize = parseInt(req.headers["content-length"]);
      const allowedTypes = ["image/png", "image/jpeg", "image/jpg", "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"];

      if (!allowedTypes.includes(file.mimetype)) {
        const message = `${file.originalname} je chybný súbor. Odosielajte iba png/jpeg/jpg alebo pdf/doc.`;
        return callback(message, null);
      }
      
      if (fileSize > maxSize) {
        const tooLargeMessage = `${file.originalname} je chybný súbor. Odosielajte súbory do 5MB.`;
        return callback(tooLargeMessage, null);
      }  

      const newName = slugify(file.originalname);
      const filename = `${formatedDate}_${newName}`;
      callback(null, filename);
    }
  });

  const uploadFiles = multer({ storage: storage, limits: { fileSize: maxSize } }).array("multi-files", 4);
  return util.promisify(uploadFiles);
}

module.exports = createUploadMiddleware;

然后修改控制器,先解析表单获取参数(需要额外使用multer().none()先解析文本字段):

const multer = require('multer');
const createUploadMiddleware = require('../path/to/upload');

async post(req, res) {
  try {
    // 先解析文本字段
    await multer().none()(req, res);
    
    // 获取参数并创建上传中间件
    const { selfGovernmentName, week } = req.body;
    const upload = createUploadMiddleware(selfGovernmentName, week);
    
    // 执行文件上传
    await upload(req, res);

    // 后续逻辑保持不变,优化为async/await写法
    const actualDate = new Date();
    const formatedDate = moment(actualDate).format("DD.MM.YYYY, HH:mm:ss");
    
    const content = await Content.create({
      creationDate: formatedDate,
      dateRange: req.body.dateRange,
      sourceContent: req.body.sourceContent,
      outputContent: req.body.outputContent,
      selfGovernmentName: req.body.selfGovernmentName,
      createdBy: req.body.createdBy
    });

    const checkContent = await Content.findOne({ where: { id: content.id } });
    if (!checkContent) {
      return res.status(400).send({ message: 'Obsah so zadaným ID sa nenašiel.' });
    }

    for (const file of req.files) {
      await ContentAttachment.create({
        fileName: file.filename,
        mimeType: file.mimetype,
        contentId: content.id,
        path: file.destination,
      });
    }

    res.status(200).send({ message: "Obsah bol úspešne pridaný." });
  } catch (err) {
    console.error(err);
    if (err.message.includes("缺少")) {
      res.status(400).send({ error: err.message });
    } else {
      res.status(500).send({ error: "Nepodarilo sa pridať obsah z dôvodu: ", err });
    }
  }
}

额外优化:控制器代码简化

原控制器使用了多层then回调,建议改为async/await写法,代码更清晰易维护,如上面示例所示。

内容的提问来源于stack exchange,提问作者Marek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 02:57:17