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

NestJS中跨GRPC/GraphQL向服务透传租户ID的正确方案

NestJS 跨gRPC/GraphQL场景统一透传租户ID最佳实践

这套方案可以实现零手动传参、零重复解析代码、全协议兼容,不需要在服务层直接操作ExecutionContext,也不需要写冗余的透传逻辑,完全符合NestJS设计规范。


优先方案:基于AsyncLocalStorage实现全局请求上下文(无性能损耗)

Node.js原生的AsyncLocalStorage可以在异步调用链中独立存储请求级数据,完全和上层协议解耦,不需要依赖Nest的请求作用域注入(请求作用域会导致服务实例每次请求重建,高并发下有明显性能开销)。

实现步骤

  • 第一步:编写单例上下文服务
import { Injectable } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';

export interface TenantInfo {
  token: string;
  id: string;
}

@Injectable()
export class RequestContextService {
  private readonly als = new AsyncLocalStorage<TenantInfo>();

  // 拦截器/守卫层调用,初始化当前请求的租户信息
  runWithTenant(tenantInfo: TenantInfo, callback: () => void) {
    this.als.run(tenantInfo, callback);
  }

  // 服务层直接调用获取租户信息,不需要传任何参数
  getTenant(): TenantInfo {
    const tenant = this.als.getStore();
    if (!tenant) {
      throw new Error('Cannot get tenant info outside request scope');
    }
    return tenant;
  }
}
  • 第二步:编写全局守卫,在请求入口统一解析租户ID
    你已经实现的getTenantId多协议解析逻辑直接放在全局守卫里即可,所有请求进来先解析租户信息,存入ALS上下文,后续业务逻辑完全不需要再处理解析逻辑:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql';
import { RequestContextService } from './request-context.service';
// 引入已有的parseTokenTenantInfo、logger方法即可

@Injectable()
export class TenantAuthGuard implements CanActivate {
  constructor(private readonly ctxService: RequestContextService) {}

  canActivate(context: ExecutionContext): Promise<boolean> {
    return new Promise((resolve) => {
      const tenantInfo = this.parseTenantFromContext(context);
      this.ctxService.runWithTenant(tenantInfo, () => {
        resolve(true);
      });
    });
  }

  private parseTenantFromContext(context: ExecutionContext) {
    if (context.getType() === 'rpc') {
      logger.debug('received rpc request');
      const rpcCtx = context.switchToRpc().getContext();
      const token = rpcCtx.get("x-authorization");
      return { token, id: parseTokenTenantInfo(token) };
    }
    if (context.getType<GqlContextType>() === 'graphql') {
      logger.debug('received graphql request');
      const gqlCtx = GqlExecutionContext.create(context);
      const request = gqlCtx.getContext().request;
      const token = request.headers.authorization;
      return { token, id: parseTokenTenantInfo(token) };
    }
    throw new Error(`Unknown context type receiving in tenant guard`);
  }
}
  • 第三步:全局注册守卫,确保所有请求都经过租户解析
    在公共模块中注册全局守卫即可,不需要在每个Controller/Resolver上单独添加:
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    RequestContextService,
    {
      provide: APP_GUARD,
      useClass: TenantAuthGuard,
    },
  ],
  exports: [RequestContextService],
})
export class CommonModule {}
  • 业务层使用方式
    所有服务保持单例,直接注入RequestContextService就能拿到当前请求的租户信息,完全不需要手动传参,也不需要关心当前请求是gRPC还是GraphQL:
@Injectable()
export class MyService {
  constructor(private readonly ctxService: RequestContextService) {}

  myMethod1() {
    // 直接获取租户信息,无额外传参
    const { id: tenantId, token } = this.ctxService.getTenant();
    // 后续业务逻辑直接使用tenantId即可
  }
}

备选方案:适配REQUEST注入的请求作用域上下文

如果项目不方便使用AsyncLocalStorage,可以通过适配GraphQL上下文的方式解决@Inject(REQUEST)不兼容GraphQL的问题,缺点是依赖这个上下文的服务都会变成请求作用域,高并发场景下有性能损耗。

核心配置

首先在GraphQL模块配置中,把request对象挂到上下文根节点,确保@Inject(REQUEST)能拿到统一结构的请求:

GraphQLModule.forRoot({
  // 其他配置省略
  context: ({ req }) => ({ req }),
})

然后编写统一的租户上下文提供者,在工厂函数中统一解析租户信息,服务层直接注入解析好的租户信息即可,不需要自己处理ExecutionContext:

@Module({
  providers: [
    {
      provide: 'TENANT_INFO',
      scope: Scope.REQUEST,
      inject: [REQUEST, CONTEXT],
      useFactory: (req, context: ExecutionContext) => {
        // 复用已有的getTenantId解析逻辑即可
        return getTenantId(context);
      },
    },
  ],
  exports: ['TENANT_INFO'],
})
export class CommonModule {}

服务层使用时直接通过@Inject('TENANT_INFO')注入即可拿到租户信息,不需要重复编写解析逻辑。


方案优势

  • 所有租户解析逻辑统一收敛在请求入口层,完全消除重复的解析、透传代码
  • 机制上保证只要请求经过全局守卫,租户信息一定存在,从根源避免漏传问题
  • 业务层完全和协议解耦,不需要关心当前请求来源是gRPC还是GraphQL
  • 优先推荐的ALS方案没有请求作用域的性能问题,所有服务保持单例,高并发场景表现稳定

内容的提问来源于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 14:19:18