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

NestJS自定义验证器中请求作用域服务注入失败及ModuleRef报错问题

在NestJS自定义验证器中正确注入请求作用域服务的解决方案

问题背景

在NestJS自定义装饰器中结合使用moduleRef与请求作用域的自定义提供者时遇到问题:调用moduleRef.resolve()获取作用域服务时,服务始终为undefined,且触发“Cannot read property 'resolve' of undefined”错误。尝试过官方文档的请求作用域依赖注入方式,但未解决。

现有代码实现

请求作用域服务(RequestService)

import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestService {
  private isCoffee: boolean;

  setIsCoffee(drink: string) {
    this.isCoffee = ['mocha', 'espresso', 'latte'].includes(drink);
  }

  getIsCoffee(): boolean {
    return this.isCoffee;
  }
}

RequestModule

import { Module } from '@nestjs/common';
import { RequestService } from './request.service';

@Module({
  exports: [RequestService],
  providers: [RequestService],
})
export class RequestModule {}

自定义验证器(IsCoffeeValidator)

import {/*...*/}

@ValidatorConstraint({ name: 'IsCoffeeValidator', async: true })
@Injectable()
export class IsCoffeeValidator
  implements ValidatorConstraintInterface, OnModuleInit
{
  private requestService: RequestService;
  constructor(
    @Inject(REQUEST) private request: Request,
    // @Inject(RequestService) private requestService: RequestService, // 常规DI方式无效
    private moduleRef: ModuleRef,
  ) {}

  async onModuleInit() {
    const contextId = ContextIdFactory.getByRequest(this.request);
    console.log('In onModuleInit'); // 未打印,因依赖请求作用域服务
    this.requestService = await this.moduleRef.resolve(
      RequestService,
      contextId,
      { strict: false },
    );
  }
  async validate(type: string, { object }: ValidationArguments) {
    const contextId = ContextIdFactory.getByRequest(this.request);
    // 报错:“cannot read property resolve of undefined”
    this.requestService = await this.moduleRef.resolve(
      RequestService,
      contextId,
      { strict: false },
    );
    if (this.requestService.getIsCoffee()) {
      return true;
    }
  }
}

CoffeeModule

import {/*...*/}

@Module({
  controllers: [CoffeeController],
  imports: [RequestModule],
  providers: [IsCoffeeValidator, RequestService],
})
export class CoffeeModule {}

CoffeeDto

import { IsCoffee } from '../../is-coffee.decorator';

export class CoffeeDto {
  @IsCoffee()
  drink: string;
}

CoffeeController

import {/*...*/}

@Controller('coffee')
export class CoffeeController {
  constructor(
    @Inject(RequestService) private readonly requestService: RequestService,
  ) {}

  @Post()
  checkDrink(@Body() body: CoffeeDto) {
    console.log(body);
    return this.requestService.getIsCoffee(); // 验证通过后应始终返回true
  }
}

问题原因与解决步骤

核心问题

  1. 自定义验证器默认是单例作用域,但依赖了请求作用域的REQUEST对象,导致NestJS会将验证器隐式转为请求作用域,但onModuleInit在模块初始化阶段执行,此时请求上下文还未创建,无法获取有效contextId和moduleRef。
  2. moduleRef未被正确注入,或者在请求上下文外调用导致undefined。

正确实现方式

步骤1:将验证器标记为请求作用域

显式将IsCoffeeValidator设置为请求作用域,确保每次请求都创建新实例,且能获取当前请求的上下文:

import { Injectable, Scope } from '@nestjs/common';
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
import { RequestService } from './request.service';
import { REQUEST, Inject } from '@nestjs/core';
import { Request } from 'express';

@ValidatorConstraint({ name: 'IsCoffeeValidator', async: true })
@Injectable({ scope: Scope.REQUEST }) // 显式标记为请求作用域
export class IsCoffeeValidator implements ValidatorConstraintInterface {
  constructor(
    private readonly requestService: RequestService, // 直接注入请求作用域服务
    @Inject(REQUEST) private readonly request: Request,
  ) {}

  async validate(drink: string, args: ValidationArguments) {
    // 先设置当前饮品是否为咖啡
    this.requestService.setIsCoffee(drink);
    // 返回验证结果
    return this.requestService.getIsCoffee();
  }
}

步骤2:调整模块配置

在CoffeeModule中移除重复的RequestService提供者(已通过RequestModule导入并导出):

@Module({
  controllers: [CoffeeController],
  imports: [RequestModule],
  providers: [IsCoffeeValidator], // 移除重复的RequestService
})
export class CoffeeModule {}

步骤3:修正验证逻辑

原代码中验证器先调用getIsCoffee但未设置值,需先通过setIsCoffee传入当前drink参数,再返回结果。

补充说明

  • 当验证器被标记为请求作用域后,常规构造函数注入RequestService即可生效,无需再通过moduleRef.resolve()手动获取。
  • onModuleInit生命周期钩子不适用于请求作用域的提供者,因为它在模块启动时执行,而请求作用域的实例是每次请求时才创建的。

内容的提问来源于stack exchange,提问作者Alex Goncalves

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 06:45:42