Mongoose Model的pre('remove')钩子未触发问题求助
Mongoose pre('remove')钩子未触发的解决方法
问题场景
在Mongoose 7.5.0、Node.js 18+环境下,尝试通过pre('remove')钩子实现用户删除时的级联删除逻辑:删除用户的同时删除关联消息、归档记录,并冻结用户名。但执行删除操作后,用户记录正常删除,钩子内的日志输出和级联操作完全未执行,无报错信息。
触发删除的代码:
const user = await User.findById(req.user._id).select("_id"); await user.deleteOne();
用户模型中的钩子代码:
// 加密密码的save钩子 schema.pre("save", async function (next) { if (!this.isModified("password")) { next(); } const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); next(); }); // 级联删除的remove钩子 schema.pre("remove", async function (next) { const userId = this._id.toString(); console.log("🚀 ~ file: user.js:155 ~ userId:", userId); console.log("we are in cascade delete"); await this.model("message").deleteMany({ user_id: userId }); await this.model("archive").deleteMany({ user_id: userId }); const frozen = new this.model("frozenusername")({ username: this.username }); await frozen.save(); next(); });
核心原因
Mongoose的pre('remove')钩子仅在调用文档实例的remove()方法时触发,而你当前使用的deleteOne()(无论是文档实例级还是模型级调用)属于直接操作MongoDB集合的方法,不会触发文档级的钩子逻辑。
修复方案
方案1:替换deleteOne()为remove()方法
修改删除触发代码,使用文档实例的remove()方法(注意:Mongoose 6.x+标记该方法为deprecated,但仍可使用):
// 必须查询完整文档(至少包含username字段),因为钩子需要访问this.username const user = await User.findById(req.user._id); await user.remove();
注意:不能使用select("_id")限制字段,否则钩子中无法获取this.username。
方案2:改用文档级pre('deleteOne')钩子
如果想继续使用deleteOne(),可以改用文档级的deleteOne钩子,配置{ document: true }后,this指向当前文档实例,用法和pre('remove')一致:
schema.pre("deleteOne", { document: true }, async function(next) { const userId = this._id.toString(); console.log("🚀 ~ file: user.js:155 ~ userId:", userId); console.log("we are in cascade delete"); await this.model("message").deleteMany({ user_id: userId }); await this.model("archive").deleteMany({ user_id: userId }); const frozen = new this.model("frozenusername")({ username: this.username }); await frozen.save(); next(); });
触发代码保持不变即可:
const user = await User.findById(req.user._id); await user.deleteOne();
方案3:改用模型级pre('deleteOne')钩子(适合批量/直接删除)
如果不需要先查询文档,直接通过模型调用deleteOne()(如User.deleteOne({_id: req.user._id})),可以使用模型级钩子,此时this指向查询对象,需先获取用户文档再执行级联操作:
schema.pre("deleteOne", { query: true }, async function(next) { const query = this.getQuery(); const user = await this.model.findOne(query); if (!user) return next(); const userId = user._id.toString(); console.log("🚀 ~ file: user.js:155 ~ userId:", userId); console.log("we are in cascade delete"); await this.model("message").deleteMany({ user_id: userId }); await this.model("archive").deleteMany({ user_id: userId }); const frozen = new this.model("frozenusername")({ username: user.username }); await frozen.save(); next(); });
触发代码可简化为:
await User.deleteOne({ _id: req.user._id });
额外注意事项
- 你的Mongoose 7.5.0版本完全支持文档级/模型级的
deleteOne钩子。 - 若使用方案1的
remove()方法,后续可逐步迁移到deleteOne()配合对应钩子,符合Mongoose的官方推荐方向。
内容的提问来源于stack exchange,提问作者Daxcor
相关产品推荐
相关产品推荐

