NestJS如何不通过传参实现控制器向服务层传递动态对象
NestJS 无入参跨层传递请求动态值实现方案
针对Interceptor获取customId后,无需Controller方法传参即可在Service层使用的需求,有以下两种生产可用实现:
方案1:基于AsyncLocalStorage的上下文存储(优先推荐)
基于Node.js原生异步上下文能力实现,不改变服务默认单例作用域,无额外性能损耗,是目前的主流实现方案。
实现步骤:
- 封装通用请求上下文提供者
import { Injectable } from '@nestjs/common'; import { AsyncLocalStorage } from 'async_hooks'; @Injectable() export class RequestContext { private readonly als = new AsyncLocalStorage<Map<string, unknown>>(); run<T>(callback: () => T): T { const contextStore = new Map<string, unknown>(); return this.als.run(contextStore, callback); } setValue(key: string, value: unknown): void { this.als.getStore()?.set(key, value); } getValue<T = unknown>(key: string): T | undefined { return this.als.getStore()?.get(key) as T; } }
- 在原有获取
customId的Interceptor中写入上下文
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { RequestContext } from './request-context'; @Injectable() export class CustomIdInterceptor implements NestInterceptor { constructor(private readonly requestContext: RequestContext) {} intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> { // 保留原有逻辑获取customId const customId = ''; // 替换为你实际的customId获取逻辑 return this.requestContext.run(() => { this.requestContext.setValue('customId', customId); return next.handle(); }); } }
- 在根模块全局注册上下文提供者和拦截器
import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { RequestContext } from './request-context'; import { CustomIdInterceptor } from './custom-id.interceptor'; @Module({ providers: [ RequestContext, { provide: APP_INTERCEPTOR, useClass: CustomIdInterceptor, } ], exports: [RequestContext] }) export class AppModule {}
- 任意Service层直接注入上下文即可获取值,无需Controller传参
import { Injectable } from '@nestjs/common'; import { RequestContext } from './request-context'; @Injectable() export class BusinessService { constructor(private readonly requestContext: RequestContext) {} async handleBusiness() { const customId = this.requestContext.getValue<string>('customId'); // 直接使用customId实现业务逻辑即可 } }
该方案同一条请求链路下的所有异步调用都能准确获取对应请求的customId,不会出现并发场景下值串扰的问题。
方案2:Nest原生请求作用域注入
基于Nest自带的请求作用域提供者实现,无需手动封装异步存储,但所有注入该自定义值的服务都会变为请求作用域,高并发场景下会有实例化的性能开销,适合并发量不高的场景使用。
实现步骤:
- 定义自定义注入令牌常量
export const CUSTOM_ID_PROVIDER = Symbol('CUSTOM_ID');
- 在Interceptor中将获取到的
customId挂载到请求对象上
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { CUSTOM_ID_PROVIDER } from './constants'; @Injectable() export class CustomIdInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> { const request = context.switchToHttp().getRequest(); // 保留原有逻辑获取customId request[CUSTOM_ID_PROVIDER] = ''; // 替换为你实际的customId获取逻辑 return next.handle(); } }
- 在模块中注册请求作用域的
customId提供者
import { Module, Scope, REQUEST } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CUSTOM_ID_PROVIDER } from './constants'; import { CustomIdInterceptor } from './custom-id.interceptor'; import { BusinessService } from './business.service'; @Module({ providers: [ BusinessService, { provide: APP_INTERCEPTOR, useClass: CustomIdInterceptor, }, { provide: CUSTOM_ID_PROVIDER, scope: Scope.REQUEST, inject: [REQUEST], useFactory: (request: Request) => request[CUSTOM_ID_PROVIDER], } ] }) export class AppModule {}
- Service层直接注入即可使用
import { Injectable, Inject } from '@nestjs/common'; import { CUSTOM_ID_PROVIDER } from './constants'; @Injectable() export class BusinessService { constructor(@Inject(CUSTOM_ID_PROVIDER) private readonly customId: string) {} async handleBusiness() { // 直接使用this.customId实现业务逻辑即可 } }
注意:禁止使用普通全局变量存储请求级的
customId,并发请求下会出现值互相覆盖的问题。
内容的提问来源于stack exchange,提问作者youssef mahdi
相关产品推荐
相关产品推荐

