如何使用或创建Mongoose Schema的onUpdate更新中间件?
这个问题问得很到位!很多人刚接触Mongoose的时候都会困惑:既然save中间件能处理密码加密,那更新记录的时候怎么复用类似逻辑?其实核心区别在于更新类中间件的上下文不是文档实例,而是查询对象,下面我分场景给你详细讲:
1. 优先用
pre('findOneAndUpdate')处理单条更新 大部分日常更新操作(比如findByIdAndUpdate、findOneAndUpdate)都会触发这个中间件,这也是最常用的场景。和save中间件不同,这里的this是Mongoose的Query对象,不是文档实例,所以我们需要从查询对象里提取要更新的密码,加密后再塞回去:
const bcrypt = require('bcrypt'); // 假设你的Schema是这样的 const userSchema = new mongoose.Schema({ username: String, password: String }); // 处理更新时的密码加密 userSchema.pre('findOneAndUpdate', async function(next) { // 获取当前要执行的更新内容 const update = this.getUpdate(); // 检查是否要更新密码:处理两种常见写法 const passwordToHash = update.password || update.$set?.password; if (!passwordToHash) { // 如果没修改密码,直接走下一步 return next(); } try { // 生成盐并加密密码 const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(passwordToHash, salt); // 把明文密码替换成加密后的版本 if (update.password) { update.password = hashedPassword; } else if (update.$set) { update.$set.password = hashedPassword; } // 更新查询对象的内容 this.setUpdate(update); next(); } catch (err) { // 把错误传递给下一个中间件/错误处理 next(err); } });
关键要点:
- 用
async/await替代嵌套回调,代码更清晰易读 - 要兼容两种更新写法:直接
{ password: 'newPass' }和{ $set: { password: 'newPass' } } - 不要直接修改
update对象后就完事,必须用this.setUpdate()把修改后的内容同步回查询对象
2. 批量更新用
pre('updateMany')中间件 如果是用updateMany做批量更新,逻辑和上面类似,但要注意批量更新时所有匹配的文档都会被设置成同一个加密后的密码(如果你的更新操作里包含password字段的话):
userSchema.pre('updateMany', async function(next) { const update = this.getUpdate(); const passwordToHash = update.password || update.$set?.password; if (passwordToHash) { const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(passwordToHash, salt); if (update.password) { update.password = hashedPassword; } else if (update.$set) { update.$set.password = hashedPassword; } this.setUpdate(update); } next(); });
3. 避坑提醒
- 不要依赖
save中间件处理更新:除非你先通过findById拿到文档实例,修改后再调用save()(这种场景save中间件会触发),但直接用findByIdAndUpdate这类更新操作是不会触发save中间件的,效率也更低。 - 不要用
pre('update'):这个中间件是针对老式的update()方法,而且它的上下文也是查询对象,但现在更推荐用findOneAndUpdate或updateMany,所以pre('update')基本被淘汰了。
内容的提问来源于stack exchange,提问作者Marluan Espiritusanto Guerrero
相关产品推荐
相关产品推荐

