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。
原因分析
- ClsModule.forFeatureAsync使用错误:
ClsModule.forFeatureAsync用于注册与CLS上下文关联的扩展提供者,但你直接用它定义REQUEST_PROXY,该提供者并未绑定到CLS的生命周期,导致useFactory不会在请求上下文初始化时触发,自然无法生成包含请求的对象。 - 非请求作用域的实例化时机问题:
AuthZService是非请求作用域,启动时就会被实例化。此时没有请求上下文,CLS_REQ无法获取到有效请求,注入的REQUEST_PROXY自然为空。
解决方案与优化实现
方案一:直接使用ClsService(推荐)
利用ClsModule内置的ClsService直接获取请求上下文,无需自定义代理:
- 确保根模块正确初始化ClsModule(启用请求上下文中间件):
@Module({ imports: [ ClsModule.forRoot({ middleware: { mount: true, // 自动挂载CLS中间件,将请求存入上下文 }, }), ], }) export class AppModule {}
- 在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动态拦截属性访问,确保每次获取的是当前请求:
- 定义代理提供者:
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], };
- 在模块中注册该提供者:
@Global() @Module({ providers: [ AuthZService, RequestProxyProvider, ], imports: [HttpModule, ClsModule.forRoot({ middleware: { mount: true } })], exports: [AuthZService], }) export class YourModule {}
- 在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
相关产品推荐
相关产品推荐

