如何在Express请求作用域中存取已认证用户信息(TypeORM场景)
最优方案:利用Node.js AsyncLocalStorage实现请求作用域用户存储
嘿,我刚好有个完美适配你场景的简洁方案——用Node.js原生的AsyncLocalStorage来实现请求作用域的用户信息存储,既不用写繁琐的全局变量管理,也不用在每个控制器里手动处理用户字段,完全符合你的需求!
步骤1:创建请求上下文工具类
首先我们用AsyncLocalStorage封装一个请求上下文工具,用来存储和获取当前请求的用户信息:
// src/utils/request-context.ts import { AsyncLocalStorage } from 'async_hooks'; import { User } from '../entities/User'; // 导入你的User实体类型 interface RequestContext { user?: User; } const asyncLocalStorage = new AsyncLocalStorage<RequestContext>(); export const RequestContext = { // 初始化请求上下文,传入用户信息 run: (context: RequestContext, callback: () => void) => { return asyncLocalStorage.run(context, callback); }, // 获取当前请求的用户 getCurrentUser: (): User | undefined => { const context = asyncLocalStorage.getStore(); return context?.user; }, };
步骤2:添加Express中间件初始化上下文
接下来写一个Express中间件,在认证完成后把用户信息存入请求上下文,确保后续的TypeORM操作都能拿到这个用户:
// src/middlewares/request-context.middleware.ts import { Request, Response, NextFunction } from 'express'; import { RequestContext } from '../utils/request-context'; export const requestContextMiddleware = (req: Request, res: Response, next: NextFunction) => { // 假设你的认证中间件已经把用户挂载到req.user上了 // 如果用@CurrentUser装饰器,也可以在这里直接从req中提取用户(具体取决于你的认证实现) const user = req.user as User; // 初始化上下文并传入用户,执行后续中间件/控制器逻辑 RequestContext.run({ user }, next); };
记得把这个中间件加到你的Express应用里,一定要放在认证中间件之后:
// src/app.ts import express from 'express'; import { authMiddleware } from './middlewares/auth.middleware'; import { requestContextMiddleware } from './middlewares/request-context.middleware'; const app = express(); // 先执行认证中间件,再初始化请求上下文 app.use(authMiddleware); app.use(requestContextMiddleware); // 其他路由和中间件...
步骤3:修改Subscriber自动获取用户信息
现在你的Subscriber就可以直接从请求上下文里拿当前用户了,不用再手动传参:
@EventSubscriber() export class BaseSubscriber implements EntitySubscriberInterface { beforeUpdate(event: UpdateEvent<any>) { const entity = event.entity; if (entity instanceof Base) { const currentUser = RequestContext.getCurrentUser(); // 这里可以根据需求处理用户不存在的情况,比如设为'unknown'或者抛出异常 entity.lastUpdatedBy = currentUser?.username || 'unknown'; } } // 顺便给beforeInsert也加上,统一处理创建者和更新者 beforeInsert(event: InsertEvent<any>) { const entity = event.entity; if (entity instanceof Base) { const currentUser = RequestContext.getCurrentUser(); entity.createdBy = currentUser?.username || 'unknown'; entity.lastUpdatedBy = currentUser?.username || 'unknown'; } } }
步骤4:控制器里无需额外操作
最后,你的控制器方法完全不用改动(或者只需要去掉手动传用户的逻辑),Subscriber会自动处理字段填充:
@Put('/entity') public updateEntity( @CurrentUser({required: true}) user: User, @Body() entity: Entity ): Promise<Entity> { Log.info('User successfully authenticated: ' + user.username); // 直接调用save/update,Subscriber会自动给lastUpdatedBy赋值 return this.manager.save(entity); }
为什么这个方案最优?
- 原生API,无额外依赖:
AsyncLocalStorage是Node.js v14+内置的API,不用装任何第三方包,轻量可靠 - 真正的请求隔离:每个请求的上下文完全独立,不会出现跨请求的用户信息污染问题
- 符合DRY原则:不用在每个控制器/服务方法里手动传递用户信息,一次配置全局生效
- 生命周期自动管理:请求结束后上下文会自动销毁,不用手动清理
内容的提问来源于stack exchange,提问作者Sascha
相关产品推荐
相关产品推荐

