如何将Express的req.user传入Sequelize全局钩子自动填充用户字段
Sequelize全局自动注入createdBy/updatedBy实现方案
核心思路是利用Node.js原生的异步上下文存储能力,在请求链路中全局保存当前用户信息,钩子函数直接从上下文读取用户ID,不需要手动传参,也不需要逐个模型注册钩子。
步骤1:初始化全局异步上下文
用Node.js内置的AsyncLocalStorage实现跨异步调用的上下文共享,不需要额外安装第三方依赖,稳定性远高于早期的第三方CLS库。
// src/common/request-context.js const { AsyncLocalStorage } = require('async_hooks'); const localStorage = new AsyncLocalStorage(); module.exports = { /** * 在请求入口绑定当前用户到上下文 * @param {object} user 当前登录用户 * @param {Function} next 后续请求处理逻辑 */ runWithUser: (user, next) => localStorage.run({ user }, next), /** * 供全局钩子调用,获取当前请求上下文的用户 * @returns {object|null} 当前登录用户 */ getCurrentUser: () => localStorage.getStore()?.user || null }
步骤2:Express中间件绑定上下文
在鉴权中间件之后、业务路由之前加一层全局中间件,把解析好的req.user绑定到异步上下文里,整个请求链路内的所有异步操作都能直接读取到这个用户,不需要层层传参。
const { runWithUser } = require('./common/request-context'); // 注意:这个中间件要放在鉴权中间件之后,确保req.user已经被解析完成 app.use((req, res, next) => { runWithUser(req.user || null, next); });
步骤3:注册全局Sequelize钩子
直接在sequelize实例上注册全局钩子,不需要逐个模型配置,钩子函数直接从上下文读取用户ID自动赋值,和内置timestamps的自动注入逻辑完全一致。
const { getCurrentUser } = require('./common/request-context'); // 单条创建自动填充createdBy、updatedBy function autoSetCreateUser(instance) { const user = getCurrentUser(); if (user?.id) { instance.createdBy = user.id; instance.updatedBy = user.id; } } // 更新操作自动填充updatedBy function autoSetUpdateUser(instance) { const user = getCurrentUser(); if (user?.id) { instance.updatedBy = user.id; } } // 注册单实例操作钩子 sequelize.addHook('beforeCreate', autoSetCreateUser); sequelize.addHook('beforeUpdate', autoSetUpdateUser); // 注册批量操作钩子(Sequelize批量操作默认不触发单实例钩子,需要单独处理) sequelize.addHook('beforeBulkCreate', (instances) => { const user = getCurrentUser(); if (user?.id) { instances.forEach(instance => { instance.createdBy = user.id; instance.updatedBy = user.id; }); } }); sequelize.addHook('beforeBulkUpdate', (options) => { const user = getCurrentUser(); if (user?.id) { // 批量更新是直接生成SQL执行,需要手动把updatedBy加到更新字段里 options.fields = options.fields || []; if (!options.fields.includes('updatedBy')) options.fields.push('updatedBy'); options.attributes = options.attributes || {}; options.attributes.updatedBy = user.id; } });
注意事项
- 配置完成后所有调用
model.create()/model.update()/model.bulkCreate()/model.bulkUpdate()的地方都不需要手动传createdBy、updatedBy字段,会自动注入,完全不需要修改现有业务代码。 - 非请求场景(比如定时任务、初始化脚本)操作数据库时,可以手动调用
runWithUser()指定系统用户,避免字段为空。 - 如果需要在批量删除时记录操作人,可以按照
beforeBulkUpdate的逻辑新增beforeBulkDestroy钩子即可。
内容的提问来源于stack exchange,提问作者Tiza ls2erch
相关产品推荐
相关产品推荐

