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

MERN栈Express中Nodemailer路由处理程序req.body未定义问题:实现邮件发送与MongoDB数据存储的解决方案

问题分析

你的问题根源在于请求体解析的时机:当表单包含文件时,请求类型是multipart/form-data,这种格式的请求体无法被body-parser解析,必须由multer完成文件上传处理后,req.body才会被正确填充。你在upload回调函数之外就尝试读取req.body,此时multer还没完成解析,所以拿到的是undefined。

解决方案

把创建Project实例、保存数据库以及更新用户积分的逻辑,全部移到multer的upload回调函数内部(也就是else块里),确保这些操作在req.body已经被解析完成后再执行。同时还要修正nodemailer.sendMail的异步处理问题,因为它默认是回调形式,直接用await不会生效。

下面是修改后的完整代码:

const nodemailer = require('nodemailer');
const { google } = require('googleapis');
const multer = require('multer');
const fs = require('fs');
const util = require('util'); // 新增,用于promisify回调函数
const bodyParser = require('body-parser');
const keys = require('../config/keys');
const requireLogin = require('../middlewares/requireLogin');
const requireCredits = require('../middlewares/requireCredits');
const Project = mongoose.model('projects');

const oAuth2Client = new google.auth.OAuth2(keys.gmailerClientID, keys.gmailerClientSecret, keys.gmailerRedirectURI);
oAuth2Client.setCredentials({ refresh_token: keys.gmailerRefreshToken });

// 把sendMail转成promise形式,方便用await
const sendMail = util.promisify(nodemailer.createTransport({
    service: 'gmail',
    auth: {
        type: 'OAuth2',
        user: 'mail@gmail.com',
        clientId: keys.gmailerClientID,
        clientSecret: keys.gmailerClientSecret,
        refreshToken: keys.gmailerRefreshToken,
    }
}).sendMail.bind(nodemailer.createTransport({
    service: 'gmail',
    auth: {
        type: 'OAuth2',
        user: 'mail@gmail.com',
        clientId: keys.gmailerClientID,
        clientSecret: keys.gmailerClientSecret,
        refreshToken: keys.gmailerRefreshToken,
    }
})));

var Storage = multer.diskStorage({
    destination: function(req, file, callback) {
        callback(null, './images')
    },
    filename: function(req, file, callback) {
        callback(null, file.fieldname + "_" + Date.now() + "_" + file.originalname)
    }
});

var upload = multer({ storage: Storage }).single('image');

module.exports = (app) => {
    app.post('/api/sendemail', requireLogin, requireCredits, (req, res) => {
        upload(req, res, async function(err){
            if(err){
                console.log(err);
                return res.status(422).end("Something went wrong with file upload");
            } 

            // 现在req.body已经被multer解析完成,可以正常读取了
            console.log(req.body.projectTitle);
            const output = `
            <ul>
                <li>${req.body.serviceSelect}</li>
                <li>${req.body.projectOwner}</li>
                <li>${req.body.projectTitle}</li>
                <li>${req.body.descriptionBox}</li>
                <li>${req.body.teamSelector}</li>
                <li>${req.body.deliverySpeed}</li>
                <li>${req.body.deadline}</li>
                <li>${req.body.timedl}</li>
                <li>${req.body.timezone}</li>
            </ul>
            `;
            const to = "mail@outlook.com";
            const subject = req.body.projectTitle;
            let path = req.file?.path; // 可选链,避免文件不存在时报错

            try {
                // 1. 先保存项目到数据库
                const project = new Project({
                    projectTitle: req.body.projectTitle,
                    serviceSelect: req.body.serviceSelect,
                    projectOwner: req.body.projectOwner,
                    status: "Pending",
                    slack: "Slack invite link",
                    assets: "Cloud Storage asset link",
                    _user: req.user.id,
                    dateSent: Date.now()
                });
                await project.save();

                // 2. 更新用户积分
                req.user.mailCredits -= 1;
                const user = await req.user.save();

                // 3. 获取OAuth2访问令牌并发送邮件
                const accessToken = await oAuth2Client.getAccessToken();
                await sendMail({
                    from: 'mail@gmail.com',
                    to: to,
                    subject: subject,
                    html: output,
                    attachments: path ? [{ path: path }] : []
                });

                console.log("Email sent");
                // 4. 删除上传的文件
                if(path) {
                    fs.unlinkSync(path);
                    console.log("File deleted");
                }

                // 5. 返回响应
                res.send(user);
                // 如果你需要跳转,可以用res.redirect,但注意send和redirect不能同时用,选一个
                // res.redirect('/result.html');
            } catch (err) {
                console.log(err);
                // 出错时也要清理文件
                if(path) fs.unlinkSync(path);
                res.status(422).send(err);
            }
        });
    });
};
关键修改点说明
  1. 逻辑位置调整:把数据库操作和积分更新移到upload回调内部,确保req.body已被解析。
  2. 异步处理修正:用util.promisify把nodemailer.sendMail转成Promise形式,这样可以用await正确处理异步流程,避免回调嵌套。
  3. 错误处理增强:在任何出错的情况下都清理上传的文件,避免服务器存储冗余;同时给客户端返回合适的错误状态码。
  4. 可选链使用:req.file?.path避免没有上传文件时req.file为undefined导致的报错。

另外要注意:res.send()和res.redirect()不能同时调用,因为它们都会发送响应头,你需要根据业务需求选择其中一个。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 11:09:10