Multer前自定义中间件中req.body为undefined的问题排查
问题描述
我正在开发一个包含activity模块的项目,模块包含标题、描述、flex direction、基于GridFsBucket的图片等内容。想在单个请求里先保存父activity数据,再上传图片并关联activity_id,但在multer中间件之前的createActivity中间件中,req.body的activityTitle、activityDescription、flexDirection全是undefined,导致创建activity失败。已经加了Body Parser,拆分请求(先创建activity再传图片)时能正常工作,但合并成一个请求就出问题。
相关代码如下:
创建父activity的中间件代码
const createActivity = async (req, res, next) => { const { activityTitle, activityDescription, flexDirection } = req.body console.log({ activityTitle, activityDescription, flexDirection }) try { let i=1; for(i;await Activity.findOne({ordering:i});i++) { i = i++ console.log('activity ' + i) } const activityLength = await Activity.estimatedDocumentCount() if (activityLength >= 10) { res.status(400).json({error:"You Can't Add anymore activities."}) return 0 } console.log(req.body) // if (!req.files || req.files.length == 0) { // res.status(400).json({error: 'You must Upload at least an image'}) // return 0 // } const activity = await Activity.create({ activityTitle, activityDescription, flexDirection, activity_ordering:i }) console.log(activity) next() } catch (err) { res.status(400).json({error: err}) } }
路由配置
router.post('/create-activity', createActivity, uploadNewActivity.array('create-activity', 10), async (req, res) => { console.log(req.files) try { res.json({files: req.files}) } catch (err) { res.json({error: err}) } })
multer中间件配置
const CreateActivityStorage = new GridFsStorage({ url:process.env.MONGO_URI_WEBSITE, file: (req, file) => { return new Promise((resolve, reject) => { crypto.randomBytes(16, async (err, buf) => { if (err) { reject(err) } try { // Get PARENT Activity const {activityTitle, activityDescription, flexDirection} = req.body const activity = await Activity.findOne({activityTitle: activityTitle, activityDescription: activityDescription, flexDirection: flexDirection}) // Start Assigning data to each image const filename = `${activity.activityTitle}-${file.originalname}` let i=1; for(i;await ActivityImage.findOne({activity_id:activity.id ,ordering:i});i++) { i = i++ console.log('activity image ' + i) } const imageUrl = `/activity-api/activity-image/display/${filename}` // Check if Image Exists const imgExists = await ActivityImage.findOne({filename: filename}) if (imgExists) { throw Error('This image already exists') } // Check The Number of Images in this activity const activityImageLength = await ActivityImage.estimatedDocumentCount({activity_id: activity.id}) if (activityImageLength >= 10) { reject() throw Error("You've reached maximum images") } // Create in json const activityImage = await ActivityImage.create({filename: filename, ordering:i, imageUrl:imageUrl, activity_id:activity.id}) // Create in GridFsBucket const fileInfo = { id: activityImage.id, filename: filename, bucketName: 'Activites' } resolve(fileInfo) } catch (err) { reject(err) } }) }) } }) const uploadNewActivity = multer({ storage: CreateActivityStorage })
注:GridFsBucket存储部分未执行到,已添加Body Parser,拆分请求可正常工作。
问题原因与解决方法
核心原因
当请求包含文件(multipart/form-data格式)时,普通的body-parser无法解析这类请求的表单数据,必须由multer处理。你把createActivity放在multer中间件之前,此时multer还没解析请求,所以req.body是空的。
修正方案
1. 调整中间件顺序
先让multer解析请求(获取表单字段和文件),再执行createActivity创建父activity。同时在createActivity中把生成的activity ID挂载到req上,供后续multer存储时直接使用。
2. 修改后的代码
路由配置
router.post('/create-activity', uploadNewActivity.array('create-activity', 10), createActivity, async (req, res) => { console.log(req.files) try { res.json({files: req.files, activity: req.createdActivity}) } catch (err) { res.json({error: err.message}) } })
createActivity中间件
const createActivity = async (req, res, next) => { // multer已解析请求,可正常获取表单字段 const { activityTitle, activityDescription, flexDirection } = req.body console.log({ activityTitle, activityDescription, flexDirection }) try { let i = 1; // 修复原循环逻辑错误,改用while循环更清晰 while (await Activity.findOne({ ordering: i })) { i++; console.log('activity ' + i) } const activityLength = await Activity.estimatedDocumentCount() if (activityLength >= 10) { return res.status(400).json({ error: "无法添加更多活动,最多10个" }) } const activity = await Activity.create({ activityTitle, activityDescription, flexDirection, activity_ordering: i }) console.log(activity) // 将创建好的activity挂载到req,供后续存储逻辑使用 req.createdActivity = activity next() } catch (err) { res.status(400).json({ error: err.message }) } }
multer GridFsStorage配置
const CreateActivityStorage = new GridFsStorage({ url: process.env.MONGO_URI_WEBSITE, file: (req, file) => { return new Promise(async (resolve, reject) => { try { // 直接从req获取已创建的activity,避免重复查询 const activity = req.createdActivity if (!activity) { return reject(new Error("未找到关联的活动")) } const filename = `${activity.activityTitle}-${file.originalname}` let i = 1; while (await ActivityImage.findOne({ activity_id: activity.id, ordering: i })) { i++; console.log('activity image ' + i) } const imageUrl = `/activity-api/activity-image/display/${filename}` const imgExists = await ActivityImage.findOne({ filename: filename }) if (imgExists) { return reject(new Error('该图片已存在')) } // 用countDocuments替代estimatedDocumentCount,查询更准确 const activityImageLength = await ActivityImage.countDocuments({ activity_id: activity.id }) if (activityImageLength >= 10) { return reject(new Error("该活动图片数量已达上限(最多10张)")) } const activityImage = await ActivityImage.create({ filename: filename, ordering: i, imageUrl: imageUrl, activity_id: activity.id }) const fileInfo = { id: activityImage.id, filename: filename, bucketName: 'Activites' } resolve(fileInfo) } catch (err) { reject(err) } }) } }) const uploadNewActivity = multer({ storage: CreateActivityStorage })
额外优化点
- 修复了原循环中
i = i++的逻辑错误,改用while循环更易维护 - 用
countDocuments替代estimatedDocumentCount,针对带查询条件的场景结果更准确 - 错误返回
err.message,避免泄露敏感错误信息
内容的提问来源于stack exchange,提问作者Mohamed Abd Elrahman
相关产品推荐
相关产品推荐

