Node.js与React.js中RangeError: Maximum call stack size exceeded报错排查
错误原因
- 核心原因:password虚拟属性setter死循环
你为password虚拟属性定义的setter中直接执行了this.password = password,这行代码会再次触发password虚拟属性的setter逻辑,形成无限递归调用,最终触发调用栈溢出。你提交的报错日志中反复触发auth.model.js第34行逻辑就是这个setter的循环调用导致。 - 次要问题1:用户模型配置错误
- 重复定义了
name字段,后定义的name会覆盖前一个配置,丢失了unique: true、lowercase: true的约束 - 缺失
email字段定义,无法存储用户邮箱信息 - Schema配置项拼写错误,正确写法是
timestamps: true,而非timeStamp: true - 缺失
crypto模块引入,加密密码时会报错
- 重复定义了
- 次要问题2:控制器逻辑错误
activationController中嵌套重复定义了自身,属于冗余错误代码registerController中User.findOne是异步操作,你没有等待查询结果就直接生成token发送激活邮件,即便邮箱已被占用,依然会发送激活邮件,逻辑错误activationController中已经通过jwt.verify拿到了解密后的decoded数据,不需要再重复调用jwt.decode解析token
修复方案
1. 修复用户模型代码
const mongoose = require('mongoose') // 补充缺失的crypto引入 const crypto = require('crypto') const userSchema = new mongoose.Schema({ name: { type: String, trim: true, required: true, unique: true, lowercase: true }, // 补充缺失的email字段定义 email: { type: String, trim: true, required: true, unique: true, lowercase: true }, hashed_password: { type: String, required: true }, salt: String, role: { type: String, default: 'Normal' }, resetPasswordLink: { data: String, default: '' } // 修正配置项拼写错误 }, {timestamps: true}) // 修复password虚拟属性setter死循环 userSchema.virtual('password') .set(function(password){ // 赋值给私有属性_password,避免触发虚拟属性setter this._password = password this.salt = this.makeSalt() this.hashed_password = this.encryptPassword(password) }) .get(function(){ return this._password }) userSchema.methods = { makeSalt: function(){ return Math.round(new Date().valueOf() * Math.random()) + '' }, encryptPassword: function(password){ if(!password) return '' try{ return crypto .createHmac('sha1', this.salt) .update(password) .digest('hex') } catch (err) { return '' } }, authenticate: function (plainPassword) { return this.encryptPassword(plainPassword) === this.hashed_password } } module.exports = mongoose.model('User', userSchema)
2. 修复activationController代码
exports.activationController = (req, res) => { const { token } = req.body; if (token) { jwt.verify(token, process.env.JWT_ACCOUNT_ACTIVATION, (err, decoded) => { if (err) { return res.status(401).json({ errors: 'Expired link. Signup again' }); } // 直接使用verify返回的decoded结果,无需重复解析 const { name, email, password } = decoded; const user = new User({ name, email, password }); user.save((err, user) => { if (err) { return res.status(401).json({ errors: 'User cannot be saved.' }); } return res.json({ success: true, message: 'Signup success' }); }); }); } else { return res.json({ message: 'Please try again.' }); } };
3. 修复registerController异步逻辑
exports.registerController = (req, res) => { const { name, email, password } = req.body; const errors = validationResult(req); if (!errors.isEmpty()) { const firstError = errors.array().map(error => error.msg)[0]; return res.status(422).json({ errors: firstError }); } User.findOne({ email }).exec((err, user) => { if (err) { return res.status(500).json({ errors: '服务器内部错误' }); } if (user) { return res.status(400).json({ errors: 'Email is taken' }); } // 确认邮箱未被占用后再生成token发送邮件 const token = jwt.sign( { name, email, password }, process.env.JWT_ACCOUNT_ACTIVATION, { expiresIn: '5m' } ); const emailData = { from: process.env.EMAIL_FROM, to: email, subject: 'Account activation link', html: ` <h1>Please use the following to activate your account</h1> <button style="padding: 15px; background-color: blue; text-align:center; opacity: 0.2; outline: none; border-radius: 10px;"><a style="text-decoration: none; color: white;" href="${process.env.CLIENT_URL}/users/activate/${token}">Activate your account</a></button> <hr /> <p>This email may contain sensitive information</p> <p>${process.env.CLIENT_URL}</p> ` }; // 此处补充调用邮件发送服务的逻辑 }); };
内容的提问来源于stack exchange,提问作者Afolabi Opeyemi
相关产品推荐
相关产品推荐

