在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
相关产品推荐
相关产品推荐

