如何在MongoDB pre-save钩子中自动设置操作用户等额外信息
问题解答
首先明确:可以向pre-save方法传递自定义参数,针对你的场景有两种常用实现方案:
方案1:手动传参(适合少部分表需要填充操作人场景)
Mongoose 提供了$locals属性专门用于在实体实例和钩子之间传递自定义数据,这部分数据不会被持久化到数据库,也不会和Schema定义的字段冲突。
改造步骤
- 调用save方法前,将当前操作用户ID写入实体的
$locals属性:
// 示例:接口中拿到登录用户ID后实例化实体 const newRole = new Role({ name: '运营' }); newRole.$locals.operatorId = loginUser._id; // loginUser是当前登录的用户对象 await newRole.save();
- 修改pre-save钩子,读取
$locals中的操作用户ID:
RoleSchema.pre("save", async function (done) { const operatorId = this.$locals.operatorId; // 可选校验:避免遗漏传入操作用户ID if (!operatorId) return done(new Error('操作人ID不能为空')); if(!this.isNew){ this.set('modified_at', new Date()); this.set('modified_by', operatorId); } else { this.set('created_at', new Date()); this.set('created_by', operatorId); } done(); });
方案2:自动注入(适合全表通用审计字段场景)
如果你的项目是Express/Koa类的后端服务,大部分表都需要存创建人、修改人信息,可以通过Mongoose插件 + 请求上下文的方式实现全自动填充,不需要业务代码手动传参。
实现逻辑
- 用Node.js的
async_hooks封装请求上下文存储,每个请求的登录用户信息存在上下文里。 - 写一个通用的审计插件,给所有需要的Schema注册:
const mongoose = require('mongoose'); // 假设你已经封装好了从请求上下文取登录用户ID的方法getLoginUserId const { getLoginUserId } = require('../utils/request-context'); const auditPlugin = (schema) => { // 自动给Schema添加审计字段,不用每个Schema重复定义 schema.add({ created_at: Date, created_by: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, modified_at: Date, modified_by: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } }); schema.pre('save', function (done) { const operatorId = getLoginUserId(); if (!operatorId) return done(new Error('未获取到登录用户信息')); if(!this.isNew){ this.modified_at = new Date(); this.modified_by = operatorId; } else { this.created_at = new Date(); this.created_by = operatorId; } done(); }); // 可选:适配update系列方法的审计字段填充,save钩子不会触发update类操作 schema.pre(['updateOne', 'findOneAndUpdate'], function (done) { const operatorId = getLoginUserId(); this.set({ modified_at: new Date(), modified_by: operatorId }); done(); }); }; // 给需要的Schema注册插件 RoleSchema.plugin(auditPlugin);
注意事项
- 不要直接给实体实例挂载自定义属性传参,容易和Schema字段冲突甚至被意外存入数据库,优先用官方提供的
$locals。 - 如果用到
updateOne、findOneAndUpdate等非save的更新方法,需要单独添加对应pre钩子,逻辑和save钩子类似。
内容的提问来源于stack exchange,提问作者Faabass
相关产品推荐
相关产品推荐

