Mongoose pre-save钩子中isModified('password')始终返回false求助
先帮你拆解下核心问题:isModified('password')始终返回false,根源是你的路由逻辑和Mongoose的工作机制不匹配,具体原因有两个:
1. findOneAndUpdate的行为导致字段未被标记为修改
在/changepw路由里,你先用findOneAndUpdate把req.body直接更新到数据库,还设置了{ new: true }——这意味着返回的user对象已经是数据库更新后的版本了。此时你再执行user.password = req.body.password,这个赋值根本没改变user对象里password的当前值(因为它已经被findOneAndUpdate设成了req.body.password),所以isModified('password')自然返回false。
另外要注意:findOneAndUpdate是直接操作数据库的,不会触发Mongoose的pre-save中间件,这也是你一开始的更新没自动哈希密码的原因。
2. 路由逻辑冗余且不合理
不管是/changepw还是/edit路由,你都在findOneAndUpdate之后又调用了user.save(),这不仅多余,还可能导致不必要的中间件触发,甚至覆盖之前的更新操作。
针对性解决方案
方案一:重构密码修改路由,直接操作文档实例
既然要触发pre-save钩子哈希密码,那就直接查询用户实例,修改密码后再保存,这样isModified就能正确检测到字段变化:
router.post('/changepw', isAuthenticated, async function (req, res, next) { try { // 先查询获取用户实例 const user = await User.findById(req.user._id); if (!user) { return res.status(404).send('用户不存在'); } // 仅当请求里有password时才修改 if (req.body.password) { user.password = req.body.password; await user.save(); // 这里会触发pre-save钩子,isModified('password')会返回true } res.redirect('/profile'); } catch (err) { next(err); } });
方案二:优化pre-save钩子逻辑
确保钩子只在密码字段真正被修改时执行哈希,同时兼容创建用户和修改密码的场景:
userSchema.pre('save', function(next) { // 只有password字段被修改时才执行哈希 if (this.isModified('password')) { this.password = bcrypt.hashSync(this.password, bcrypt.genSaltSync(8)); } next(); });
方案三:简化编辑信息路由,移除冗余的save()调用
你的/edit路由里,findOneAndUpdate已经完成了字段更新,没必要再调用user.save(),而且graduated字段的判断可以直接整合到更新数据里:
router.post('/edit', isAuthenticated, async function (req, res, next) { try { const updateData = { name: req.body.name, phone: req.body.phone, classc: req.body.classc, major: req.body.major, minor: req.body.minor, linkedin: req.body.linkedin, bio: req.body.bio, graduated: typeof req.body.graduated !== 'undefined' }; await User.findOneAndUpdate({ _id: req.user._id }, { $set: updateData }, { new: true }); res.redirect('/profile'); } catch (err) { next(err); } });
额外补充:如果必须用findOneAndUpdate更新密码
如果你需要保留findOneAndUpdate的用法,并且希望触发哈希逻辑,可以添加pre('findOneAndUpdate')钩子:
// 新增针对findOneAndUpdate的钩子 userSchema.pre('findOneAndUpdate', function(next) { const password = this.getUpdate().$set.password; if (password) { this.getUpdate().$set.password = bcrypt.hashSync(password, bcrypt.genSaltSync(8)); } next(); }); // 修改密码路由调整选项 router.post('/changepw', isAuthenticated, async function (req, res, next) { try { await User.findOneAndUpdate( { _id: req.user._id }, { $set: req.body }, { new: true, runValidators: true, context: 'query' } ); res.redirect('/profile'); } catch (err) { next(err); } });
这种方式适合批量更新的场景,但如果只是修改密码,第一种直接操作文档实例的方案会更直观、更好维护。
内容的提问来源于stack exchange,提问作者Scott Kim

