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

NestJS使用ClsModule注入REQUEST_PROXY为空及useFactory未触发问题

NestJS非请求作用域服务获取Request对象问题解答

问题描述

我有一个NestJS模块,导入ClsModule的代码如下:

@Global()
@Module({
  providers: [
    //... 其他无关提供者
    AuthZService,
  ],
  imports: [
    HttpModule,
    ClsModule.forFeatureAsync({
      provide: REQUEST_PROXY, // <-- 我定义的常量
      inject: [CLS_REQ],
      useFactory: (request: Request) => {
        return {
          req: request,
        };
      },
      global: true, // <-- 不确定是否需要,两种方式都试过
    }),
  ],
  exports: [AuthZService],
})

随后尝试在AuthZService中使用该代理:

@Injectable()
export class AuthZService {
  constructor(
    // ... 其他可正常工作的依赖
    @Inject(REQUEST_PROXY) private readonly requestProxy: { req: Request },
  ) {}

  private get clsRequest(): ProfileRequest {
    return this.requestProxy.req;
  }

  // ...
}

代码可编译启动,但在return this.requestProxy.req;处打断点时,注入的对象为空,useFactory的断点也从未触发。同时需要实现:将当前Request对象注入到非请求作用域服务中,不希望将服务设为请求作用域,仅通过代理获取当前Request。

原因分析

  1. ClsModule.forFeatureAsync使用错误:ClsModule.forFeatureAsync用于注册与CLS上下文关联的扩展提供者,但你直接用它定义REQUEST_PROXY,该提供者并未绑定到CLS的生命周期,导致useFactory不会在请求上下文初始化时触发,自然无法生成包含请求的对象。
  2. 非请求作用域的实例化时机问题:AuthZService是非请求作用域,启动时就会被实例化。此时没有请求上下文,CLS_REQ无法获取到有效请求,注入的REQUEST_PROXY自然为空。

解决方案与优化实现

方案一:直接使用ClsService(推荐)

利用ClsModule内置的ClsService直接获取请求上下文,无需自定义代理:

  1. 确保根模块正确初始化ClsModule(启用请求上下文中间件):
@Module({
  imports: [
    ClsModule.forRoot({
      middleware: {
        mount: true, // 自动挂载CLS中间件,将请求存入上下文
      },
    }),
  ],
})
export class AppModule {}
  1. 在AuthZService中注入ClsService并获取请求:
@Injectable()
export class AuthZService {
  constructor(private readonly cls: ClsService) {}

  private get clsRequest(): ProfileRequest {
    // 每次调用时从CLS上下文获取当前请求
    return this.cls.get(CLS_REQ);
  }

  // 业务方法示例
  checkPermission() {
    const request = this.clsRequest;
    // 基于request处理权限逻辑
  }
}

方案二:自定义请求代理(按需使用)

如果需要封装成自定义代理,通过Proxy动态拦截属性访问,确保每次获取的是当前请求:

  1. 定义代理提供者:
export const REQUEST_PROXY = Symbol('REQUEST_PROXY');

export const RequestProxyProvider = {
  provide: REQUEST_PROXY,
  useFactory: (cls: ClsService) => {
    return new Proxy({}, {
      get(_, prop) {
        // 每次访问属性时从CLS上下文拉取最新请求
        const req = cls.get(CLS_REQ);
        return req?.[prop as keyof Request];
      },
    }) as Request;
  },
  inject: [ClsService],
};
  1. 在模块中注册该提供者:
@Global()
@Module({
  providers: [
    AuthZService,
    RequestProxyProvider,
  ],
  imports: [HttpModule, ClsModule.forRoot({ middleware: { mount: true } })],
  exports: [AuthZService],
})
export class YourModule {}
  1. 在AuthZService中注入使用:
@Injectable()
export class AuthZService {
  constructor(@Inject(REQUEST_PROXY) private readonly requestProxy: Request) {}

  private get clsRequest(): ProfileRequest {
    return this.requestProxy;
  }

  // ... 业务逻辑
}

核心原理

CLS通过中间件在请求进入时创建上下文并存储请求对象,非请求作用域服务通过ClsService可以在任意时刻获取当前请求的上下文。代理方式则通过Proxy拦截属性访问,动态从CLS上下文获取最新请求,避免了请求作用域服务带来的性能开销。

内容的提问来源于stack exchange,提问作者Mike Christensen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 03:43:21