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
相关产品推荐
相关产品推荐

