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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 02:25:38