NestJS自定义参数装饰器无法在构造函数中使用怎么办
问题根因
createParamDecorator 生成的自定义参数装饰器,仅能在Controller路由处理方法、GraphQL解析器这类Nest识别为请求处理入口的方法参数上使用,不能直接用在@Injectable()标记的Service等Provider的构造函数参数上。
Nest的依赖注入容器不会识别构造函数上的这类参数装饰器,也无法为其注入对应值,所以会抛出依赖解析失败的错误;这类装饰器本身也不是Provider类型,自然无法注册到模块的providers数组中。
另外你的代码里还有一处笔误:定义的装饰器名为TenantInfo,构造器中使用时写的是@TenantId(),二者命名不匹配,后续修正时也要注意统一。
正确实现方案
核心思路是把「从请求上下文提取租户信息」的逻辑抽成可复用的公共逻辑,同时保留给Controller用的参数装饰器,Service层通过自定义请求作用域Provider获取租户信息,二者复用同一套提取逻辑避免重复代码。
步骤1:定义公共类型、提取逻辑和Controller用装饰器
// src/tenant/tenant.type.ts import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { Request } from 'express'; // GRPC场景替换为对应GRPC请求类型即可 export type TenantInfo = { token: string id: string } /** * 从请求上下文提取租户信息的公共逻辑,装饰器和Provider都复用这个方法 */ export const extractTenantFromContext = (ctx: ExecutionContext): TenantInfo => { // HTTP场景取request对象 const request = ctx.switchToHttp().getRequest<Request>(); // GRPC场景用下面的写法替换 // const metadata = ctx.switchToRpc().getContext().getMetadata(); return { id: request.headers['x-tenant-id'] as string, token: (request.headers['authorization'] || '').replace('Bearer ', '') } } // 保留这个装饰器,给Controller路由方法参数用 export const TenantInfo = createParamDecorator( (_data: unknown, context: ExecutionContext): TenantInfo => extractTenantFromContext(context), );
步骤2:在模块中注册请求作用域的租户信息Provider
// src/tenant/tenant.module.ts import { Module, Scope, REQUEST, ExecutionContext } from '@nestjs/common'; import { TenantService } from './tenant.service'; import { extractTenantFromContext } from './tenant.type'; @Module({ providers: [ TenantService, { provide: 'TENANT_INFO', scope: Scope.REQUEST, inject: [REQUEST], useFactory: (req) => { // 构造兼容的上下文对象复用公共提取逻辑,也可以直接在这里写提取逻辑 const ctx = { switchToHttp: () => ({ getRequest: () => req }) } as ExecutionContext; return extractTenantFromContext(ctx); } } ], exports: [TenantService, 'TENANT_INFO'] }) export class TenantModule {}
步骤3:Service中注入自定义Provider获取租户信息
// src/tenant/tenant.service.ts import { Inject, Injectable, Scope } from '@nestjs/common'; import { TenantInfo } from './tenant.type'; @Injectable({ scope: Scope.REQUEST }) export class TenantService { private readonly _tenantInfo: TenantInfo; constructor( @Inject('TENANT_INFO') tenantInfo: TenantInfo, ) { this._tenantInfo = tenantInfo; console.log(`Received request from tenant id ${tenantInfo.id}`); } // 业务方法... }
Controller中使用装饰器示例
// 示例Controller @Controller('test') export class TestController { @Get() test(@TenantInfo() tenant: TenantInfo) { return tenant; } }
可选优化方案
如果你的服务并发量较高,不希望所有依赖租户信息的Service都变成请求作用域(请求作用域会让每次请求都实例化对应Provider,有一定性能开销),可以基于Node.js的AsyncLocalStorage实现请求上下文存储,结合全局中间件/拦截器提前把租户信息存入上下文,Service中直接从上下文读取即可,不需要设置请求作用域,也不需要注入REQUEST对象。
内容的提问来源于stack exchange,提问作者Fred Johnson
相关产品推荐
相关产品推荐

