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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 13:21:27