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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 04:42:49