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); } }); }); };
关键修改点说明
- 逻辑位置调整:把数据库操作和积分更新移到
upload回调内部,确保req.body已被解析。 - 异步处理修正:用
util.promisify把nodemailer.sendMail转成Promise形式,这样可以用await正确处理异步流程,避免回调嵌套。 - 错误处理增强:在任何出错的情况下都清理上传的文件,避免服务器存储冗余;同时给客户端返回合适的错误状态码。
- 可选链使用:
req.file?.path避免没有上传文件时req.file为undefined导致的报错。
另外要注意:res.send()和res.redirect()不能同时调用,因为它们都会发送响应头,你需要根据业务需求选择其中一个。
内容的提问来源于stack exchange,提问作者Codetheweb1234
相关产品推荐
相关产品推荐

