Node.js报错:Can't set headers after they are sent to the client 求助
解决注册时发送邮件报错"Can't set headers after they are sent to the client"并实现全流程邮件通知
你的错误根源有两个:
- 重复响应客户端:
sendEmail的catch块中发送响应后未终止函数执行,导致后续代码继续运行并再次发送响应,触发"已发送响应后无法设置头"的错误。 - 错误引用用户邮箱:调用
sendEmail时用了user.email,但此时user是findOne查询不到的结果(即null),应该用新创建的newUser.email,这会导致邮件发送失败进入catch,进而触发重复响应。
修正后的注册控制器代码
// controller const User = require('./../models/usermodel') const express = require('express') const bcrypt = require('bcrypt') const jwt = require('jsonwebtoken') const sendEmail = require('./../utilis/email') // signUp exports.signUp = async (req, res) => { const { name, email, password, role } = req.body try { if (!name || !email || !password || !role) { return res.status(400).json({ status: "failure", message: '请填写姓名、邮箱、密码和角色' }) } const existingUser = await User.findOne({ email }) if (existingUser) { return res.status(400).json({ status: "failure", message: '该邮箱已注册' }) } // 创建新用户 const newUser = await User.create(req.body) try { await sendEmail({ to: newUser.email, // 改用新创建的用户邮箱 message: '感谢注册,您已成功加入我们的网站!', subject: "注册成功通知" }) return res.status(201).json({ // 用201表示资源创建成功 status: "success", message: "注册成功,已发送确认邮件", data: { newUser } }) } catch (err) { // 可选:邮件发送失败时删除已创建的用户 // await User.findByIdAndDelete(newUser._id); return res.status(500).json({ // 加return终止函数执行 status: "partial-success", message: "注册成功,但邮件发送失败", error: err.message }) } } catch (error) { res.status(400).json({ status: "failure", message: error.message }) } } const signToken = (userId) => { return jwt.sign({ id: userId }, "secret access key 009", { expiresIn: "1d" }) }
关键修改点
- 将查询变量重命名为
existingUser,避免和新创建的newUser混淆 - 发送邮件时使用
newUser.email替代无效的user.email - 在
sendEmail的catch块中添加return,确保发送响应后终止函数,避免重复响应 - 调整状态码:用201表示资源创建成功,500表示邮件发送的服务器错误
- 优化响应信息,让用户清晰了解当前状态
实现登录、重置密码的邮件发送
登录时发送登录通知
在登录控制器验证密码成功后调用sendEmail:
exports.login = async (req, res) => { const { email, password } = req.body try { const user = await User.findOne({ email }) if (!user || !(await bcrypt.compare(password, user.password))) { return res.status(401).json({ status: "failure", message: "邮箱或密码错误" }) } const token = signToken(user._id) // 发送登录通知(失败不影响登录流程) try { await sendEmail({ to: user.email, message: `您的账号于${new Date().toLocaleString()}登录,若非本人操作请及时修改密码`, subject: "账号登录通知" }) } catch (err) { console.log("登录通知邮件发送失败:", err) } res.status(200).json({ status: "success", token, data: { user } }) } catch (error) { res.status(400).json({ status: "failure", message: error.message }) } }
重置密码时发送重置链接
在忘记密码流程中生成重置token后发送邮件:
exports.forgotPassword = async (req, res) => { const { email } = req.body try { const user = await User.findOne({ email }) if (!user) { return res.status(404).json({ status: "failure", message: "该邮箱未注册" }) } // 生成15分钟有效期的重置token const resetToken = jwt.sign({ id: user._id }, "reset-secret-key", { expiresIn: "15m" }) const resetUrl = `${req.protocol}://${req.get('host')}/api/v1/users/resetPassword/${resetToken}` try { await sendEmail({ to: user.email, message: `点击链接重置密码:${resetUrl}\n链接15分钟内有效`, subject: "密码重置请求" }) return res.status(200).json({ status: "success", message: "重置密码邮件已发送" }) } catch (err) { return res.status(500).json({ status: "failure", message: "邮件发送失败,请稍后重试", error: err.message }) } } catch (error) { res.status(400).json({ status: "failure", message: error.message }) } }
内容的提问来源于stack exchange,提问作者Tayyab Saith
相关产品推荐
相关产品推荐

