使用Nodemailer发送注册OTP时遭遇ERR_HTTP_HEADERS_SENT错误求助
Express注册发送OTP邮件时ERR_HTTP_HEADERS_SENT错误排查与修复
在Express中实现用户注册并通过Nodemailer发送OTP验证邮件时,触发Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client错误,核心原因是多次向客户端发送HTTP响应,同时代码还存在参数传递错误、时间计算错误等问题。
错误原因分析
- 混合使用
async/await与.then()导致重复响应:在newUser.save().then()回调中,既调用了会发送响应的sendOTPVerificationEmail,又执行return res.status(201).json(...),之后路由末尾还有另一个return res.status(201).json(...),一次请求内多次调用res.send/res.json触发错误。 sendOTPVerificationEmail参数错误:函数定义接收(req, res),但实际传入的是(newUser, res),且函数内部直接使用未定义的email、_id变量,触发catch分支的响应,与路由内的响应冲突。- OTP过期时间计算错误:
expiresAt: Date.now() * 3600000是将当前时间戳乘以3600000,逻辑错误,应改为Date.now() + 3600000(当前时间加1小时)。
修复后的代码
注册路由
app.post("/signup", async (req, res) => { const { username, email, password, cnfrmPass } = req.body; const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]).{8,}$/; if (!passwordRegex.test(password)) { return res.status(400).json({ error: '密码不符合要求' }); } try { const existingUser = await userModel.findOne({ email }); if (existingUser) { return res.status(400).json({ error: '用户已存在' }); } const newUser = new userModel({ username, email, password }); const savedUser = await newUser.save(); // 交由OTP函数统一处理响应 await sendOTPVerificationEmail(savedUser, res); } catch (error) { console.error('错误:', error); // 避免重复发送错误响应 if (!res.headersSent) { res.status(500).json({ error: '服务器内部错误' }); } } })
OTP发送函数
const sendOTPVerificationEmail = async (user, res) => { try { const { _id, email } = user; const otp = `${Math.floor(1000 + Math.random() * 9000)}`; const mailOptions = { from: process.env.AUTH_EMAIL, to: email, subject: "验证你的邮箱", html: `<p>在应用中输入 <b>${otp}</b> 以验证邮箱并完成注册</p> <p>此验证码 <b>1小时后过期</b>。</p>` }; const saltRounds = 10; const hashedOTP = await bcrypt.hash(otp, saltRounds); const newOTPVerification = new UserOTPVerification({ userId: _id, otp: hashedOTP, createdAt: Date.now(), expiresAt: Date.now() + 3600000, // 当前时间加1小时 }); await newOTPVerification.save(); await transporter.sendMail(mailOptions); res.status(201).json({ status: "PENDING", message: "验证邮件已发送", data: { userId: _id, email, } }); } catch (error) { console.error('发送OTP错误:', error); res.status(500).json({ status: "FAILED", message: "发送验证邮件失败", error: error.message }); } }
关键修复点
- 统一使用
async/await替代混合写法,避免回调嵌套导致的响应重复发送 - 调整响应逻辑:由
sendOTPVerificationEmail统一处理成功/失败的响应,路由不再额外发送响应 - 修正
sendOTPVerificationEmail的参数,从传入的user对象中提取_id和email - 修复OTP过期时间的计算逻辑
- 在catch分支中增加
res.headersSent判断,避免重复发送错误响应
内容的提问来源于stack exchange,提问作者Indul
相关产品推荐
相关产品推荐

