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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:14:33