Express应用中密码重置链接逻辑为何显示undefined?
问题原因
核心是异步代码执行顺序错误:
User.findOne()是异步数据库操作,它的回调函数会在查询完成后才触发- 你当前代码里,创建 transporter、定义 mailOptions、调用 sendMail 这些逻辑是在
User.findOne()之后同步执行的,此时回调里的link还没被赋值,所以邮件模板里的link还是初始的undefined - 控制台能打印有效链接,是因为后续异步回调执行时才完成
link的赋值和打印,但这时邮件已经发送完毕了
修复方案
方案1:将邮件逻辑移入异步回调内部
把nodemailer相关代码全部放到User.findOne的回调里,确保link赋值完成后再发送邮件:
router.post('/forgot-password', (req, res, next) =>{ let userEmail = req.body.email; // 注意:POST请求参数一般在body里,原代码用req.query.email可能有误,改成req.body.email User.findOne({'email': req.body.email}, function(err, user){ if(err) { console.log(err); return res.status(500).send("服务器错误"); } if(!user) { return res.send("该邮箱未注册"); } // 生成JWT令牌与重置链接 const secret = JWT_SECRET + user.password; const payload = { id: user._id, // Mongoose文档的ID字段是_id而非id email: userEmail }; let token = jwt.sign(payload, secret, {expiresIn: '15m'}); // 修复模板字符串写法 let link = `${process.env.URL}/${user._id}/${token}`; console.log(link); // 邮件发送逻辑移到此处 let transporter = nodemailer.createTransport({ host: 'smtp.gmail.com', port: 465, secure: true, auth: { type: 'OAuth2', clientId: authClient, clientSecret: authCSecret } }); let mailOptions = { from: '你的发送邮箱', // 替换为实际邮箱地址 to: userEmail, replyTo: '你的回复邮箱', subject: 'Reset Your Password', html: `<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"/><link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"rel="stylesheet"/><link href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/6.0.1/mdb.min.css" rel="stylesheet"/><p class="lead">Hey, it looks like you forgot your password.</p><p class="card-text">Follow the link below to reset your password.<div class="text-center"><a class="btn btn-lg btn-primary my-3" href="${link}" target="_blank">click here</a></div></p><script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/6.0.1/mdb.min.js"></script>`, auth: { user: authUser, refreshToken: authRefreshToken, accessToken: authAccessToken, expires: 3599 } }; transporter.sendMail(mailOptions, (error, info) => { if (error) { console.log(error); return res.status(500).send("发送邮件失败"); } console.log('Message sent: %s', info.messageId); console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info)); res.send("A link has been sent to your email."); }); }); });
方案2:用Async/Await改写(逻辑更清晰)
将异步操作改为async/await语法,避免回调嵌套,让代码按顺序执行:
router.post('/forgot-password', async (req, res, next) =>{ try { let userEmail = req.body.email; // 等待数据库查询结果 const user = await User.findOne({'email': userEmail}); if(!user) { return res.send("该邮箱未注册"); } // 生成令牌与链接 const secret = JWT_SECRET + user.password; const payload = { id: user._id, email: userEmail }; let token = jwt.sign(payload, secret, {expiresIn: '15m'}); let link = `${process.env.URL}/${user._id}/${token}`; console.log(link); // 创建邮件 transporter let transporter = nodemailer.createTransport({ host: 'smtp.gmail.com', port: 465, secure: true, auth: { type: 'OAuth2', clientId: authClient, clientSecret: authCSecret } }); let mailOptions = { from: '你的发送邮箱', to: userEmail, replyTo: '你的回复邮箱', subject: 'Reset Your Password', html: `<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"/><link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"rel="stylesheet"/><link href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/6.0.1/mdb.min.css" rel="stylesheet"/><p class="lead">Hey, it looks like you forgot your password.</p><p class="card-text">Follow the link below to reset your password.<div class="text-center"><a class="btn btn-lg btn-primary my-3" href="${link}" target="_blank">click here</a></div></p><script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/6.0.1/mdb.min.js"></script>`, auth: { user: authUser, refreshToken: authRefreshToken, accessToken: authAccessToken, expires: 3599 } }; // 等待邮件发送完成 await transporter.sendMail(mailOptions); console.log('Message sent successfully'); res.send("A link has been sent to your email."); } catch(err) { console.log(err); res.status(500).send("服务器错误"); } });
额外注意事项
- 原代码中
User.findOne的查询条件用了req.query.email,但POST请求的参数通常在req.body里,需确认前端参数传递方式 - Mongoose文档的ID字段默认是
_id,而非id,拼接链接时要注意字段名 - URL拼接的原写法
process.env.URL/${username.id}/${token}是错误的模板字符串语法,需改为${process.env.URL}/${user._id}/${token}`
内容的提问来源于stack exchange,提问作者Andrew Buccellato
相关产品推荐
相关产品推荐

