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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:50:27