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

Node.js报错:Can't set headers after they are sent to the client 求助

解决注册时发送邮件报错"Can't set headers after they are sent to the client"并实现全流程邮件通知

你的错误根源有两个:

  1. 重复响应客户端:sendEmail的catch块中发送响应后未终止函数执行,导致后续代码继续运行并再次发送响应,触发"已发送响应后无法设置头"的错误。
  2. 错误引用用户邮箱:调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 03:15:24