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

在Mongoose的post hook中如何获取文档保存前的旧状态?

Mongoose post hook获取文档原始状态的实现方案

Mongoose的post hook本身没有内置保存前的文档原始状态,需要搭配pre hook缓存原始数据后做对比,具体实现如下:

针对save操作的实现

核心思路

在pre('save')钩子中将原始文档缓存到当前文档的自定义属性中,进入post('save')钩子后取出缓存的原始数据和保存后的文档对比即可得到变更内容。

示例代码

const mongoose = require('mongoose');

// 示例Schema
const userSchema = new mongoose.Schema({
  name: String,
  email: String,
  age: Number
});

// pre save钩子缓存原始数据
userSchema.pre('save', function(next) {
  // 新创建的文档没有原始状态,标记为null即可
  this._originalDoc = this.isNew ? null : this.toObject();
  next();
});

// post save钩子对比变更
userSchema.post('save', function(savedDoc, next) {
  const originalDoc = this._originalDoc;
  // 跳过新创建的文档
  if (!originalDoc) {
    delete this._originalDoc;
    return next();
  }

  // 对比获取变更字段
  const changedFields = [];
  const savedObj = savedDoc.toObject();
  Object.keys(savedObj).forEach(field => {
    if (savedObj[field] !== originalDoc[field]) {
      changedFields.push({
        field: field,
        oldValue: originalDoc[field],
        newValue: savedObj[field]
      });
    }
  });

  // 此处编写根据变更执行的自定义逻辑
  console.log('本次修改的字段:', changedFields);

  // 清理缓存属性,避免内存占用
  delete this._originalDoc;
  next();
});

针对update类操作的适配

如果使用findOneAndUpdate、updateOne等直接执行更新的方法,需要单独配置query钩子:

// pre update钩子缓存原始数据
userSchema.pre('findOneAndUpdate', async function(next) {
  this._originalUpdateDoc = await this.model.findOne(this.getQuery());
  next();
});

// post update钩子对比变更
userSchema.post('findOneAndUpdate', function(updatedDoc, next) {
  const originalDoc = this._originalUpdateDoc;
  if (!originalDoc || !updatedDoc) {
    delete this._originalUpdateDoc;
    return next();
  }

  // 变更对比逻辑和save场景一致
  const changedFields = [];
  const updatedObj = updatedDoc.toObject();
  Object.keys(updatedObj).forEach(field => {
    if (updatedObj[field] !== originalDoc[field]) {
      changedFields.push({
        field: field,
        oldValue: originalDoc[field],
        newValue: updatedObj[field]
      });
    }
  });

  // 自定义逻辑
  console.log('本次更新的字段:', changedFields);

  delete this._originalUpdateDoc;
  next();
});

注意事项

  • 缓存用的自定义属性名建议加下划线前缀,避免和Schema定义的业务字段冲突
  • 上述示例仅支持一级字段的对比,如果需要对比嵌套对象、数组等复杂类型,需要自行编写递归对比逻辑
  • 缓存的原始数据使用完成后必须删除,避免不必要的内存占用
  • 使用update类钩子时,需要确保更新方法开启了返回更新后文档的配置,如findOneAndUpdate调用时传入{ new: true }

内容的提问来源于stack exchange,提问作者user1037355

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 18:30:04