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

NestJS自定义装饰器中如何注入OtpClient服务?

在NestJS中实现带依赖注入的OTP验证参数装饰器

NestJS的参数装饰器默认无法直接注入依赖,因为它运行在DI容器生命周期之外。你可以通过以下两种方式解决这个问题,推荐使用第一种符合框架设计的方案。

方案一:自定义守卫 + 参数装饰器(推荐)

守卫是NestJS中处理路由前置验证的标准方式,支持依赖注入,我们可以先在守卫中完成OTP验证,再把结果附加到请求对象,最后用参数装饰器提取结果。

1. 创建OTP验证守卫

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { OtpClient } from '../../otp/otp.client';

@Injectable()
export class OtpValidationGuard implements CanActivate {
  constructor(private readonly otpClient: OtpClient) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const { otp } = request.body;

    if (!otp) {
      throw new UnauthorizedException('OTP参数缺失');
    }

    try {
      // 调用OtpClient的验证方法
      const validationResult = await this.otpClient.validateOtp(otp);
      // 将验证结果挂载到request对象,供后续使用
      request.otpValidationResult = validationResult;
      return true;
    } catch (error) {
      throw new UnauthorizedException('OTP验证失败');
    }
  }
}

2. 创建参数装饰器提取验证结果

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const OtpValidation = createParamDecorator(
  (_: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    // 提取之前守卫存入的验证结果
    return request.otpValidationResult;
  },
);

3. 在路由中使用

import { Controller, Post, UseGuards } from '@nestjs/common';
import { OtpValidationGuard } from './otp-validation.guard';
import { OtpValidation } from './otp-validation.decorator';
import { ValidateOtpTokenResponseDTO } from './dto/validate-otp.dto';

@Controller('auth')
export class AuthController {
  @Post('verify-otp')
  // 启用OTP验证守卫
  @UseGuards(OtpValidationGuard)
  async verifyOtp(
    // 通过装饰器获取验证后的结果
    @OtpValidation() validationResult: ValidateOtpTokenResponseDTO
  ) {
    return { success: true, data: validationResult };
  }
}

方案二:直接在参数装饰器中获取ModuleRef(不推荐)

如果你坚持要在参数装饰器中直接获取OtpClient,可以通过ModuleRef实例来获取,但这种方式依赖NestJS内部参数顺序,可能在版本更新时失效。

修改后的装饰器代码

import { createParamDecorator, ExecutionContext, ModuleRef } from '@nestjs/common';
import { OtpClient } from '../../otp/otp.client';

export const ValidateOtpDecorator = createParamDecorator(
  async (_: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const { otp } = request.body;

    // 获取ModuleRef实例(HTTP上下文下,参数索引为3,不同上下文可能不同)
    const moduleRef = ctx.getArgs()[3] as ModuleRef;
    // 获取OtpClient实例
    const otpClient = moduleRef.get(OtpClient);

    return await otpClient.validateOtp(otp);
  },
);

你之前尝试失败的原因

你之前用moduleRef.get(OTP_CLIENT_ADAPTER)失败,大概率是因为没有在模块中正确注册OTP_CLIENT_ADAPTER令牌对应的提供者。如果OtpClient实现了OtpClientAdapter接口,需要在模块中注册:

@Module({
  providers: [
    {
      provide: OTP_CLIENT_ADAPTER, // 注入令牌
      useClass: OtpClient, // 对应的实现类
    },
    OtpClient,
    // 其他依赖(比如LoggerAdapter、HttpInterceptor)
  ],
})
export class OtpModule {}

内容的提问来源于stack exchange,提问作者Vinícius Britto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 05:50:36