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

NestJS自定义验证器无法注入Service:buildingZoneService为undefined

NestJS自定义验证器依赖注入失败问题

问题描述

我正尝试为NestJS中的输入类创建自定义验证器,已完成以下实现,但每次调用注入的buildingZoneService时,它始终为undefined。我已经给Constraint类添加了@Injectable装饰器,在模块中导入了所有外部依赖,并且在main.ts中调用了class-validator的useContainer函数,完全按照相关配置说明操作,但问题依然存在。

代码实现

1. 自定义验证器约束类 (is-building-id-provided.validator.ts)

// is-building-id-provided.validator.ts
import { Injectable } from "@nestjs/common";
import { AddToQueueInput } from "@warp-core/building-queue/input/add-to-queue.input";
import { BuildingZoneService } from "@warp-core/building-zone/building-zone.service";
import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";

@ValidatorConstraint({ async: true })
@Injectable()
export class IsBuildingIdProvidedConstraint implements ValidatorConstraintInterface {
    constructor(
        protected readonly buildingZoneService: BuildingZoneService
    ) {}

    async validate(buildingId: number, args: ValidationArguments) {
        if (buildingId) {
            return true;
        }

        const addToQueue = args.object as AddToQueueInput;

        const buildingZone = await this.buildingZoneService.getSingleBuildingZone(
            addToQueue.counterPerHabitat
        );

        if (!buildingZone.buildingId) {
            return false;
        } 
        
        return true;
    }

    defaultMessage(args: ValidationArguments) {
        return `Building Id is required to create new building on existing building zone`;
    }
}

export function IsBuildingIdProvided(validationOptions?: ValidationOptions) {
    return function (object: Object, propertyName: string) {
        registerDecorator({
            target: object.constructor,
            propertyName: propertyName,
            options: validationOptions,
            constraints: [],
            validator: IsBuildingIdProvidedConstraint,
        });
    };
}

2. 输入类 (add-to-queue.input.ts)

// add-to-queue.input.ts
import { Field, InputType, Int, PartialType } from "@nestjs/graphql";
import { IsBuildingIdProvided } from "@warp-core/building-queue/input/validator/is-building-id-provided.validator";
import { BuildingQueueElementModel } from "@warp-core/database/model/building-queue-element.model";

@InputType({description: "Creates new element in queue"})
export class AddToQueueInput extends PartialType(BuildingQueueElementModel)
{
    @Field(type => Int, {
        description: "Id of building type that will be constructed. If building is already placed, that field will be ignored"
    })
    @IsBuildingIdProvided()
    buildingId?: number;

    @Field(type => Int, {description: "How much levels will be build"})
    endLevel: number;
}

3. 模块配置 (building-queue.module.ts)

// building-queue.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { AuthModule } from "@warp-core/auth/auth.module";
import { BuildingQueueAddService } from "@warp-core/building-queue/building-queue-add.service";
import { BuildingQueueResolver } from "@warp-core/building-queue/building-queue.resolver";
import { IsBuildingIdProvidedConstraint } from "@warp-core/building-queue/input/validator/is-building-id-provided.validator";
import { BuildingZoneModule } from "@warp-core/building-zone/building-zone.module";
import { BuildingModule } from "@warp-core/building/building.module";
import { DatabaseModule } from "@warp-core/database/database.module";
@Module({
    providers: [
        BuildingQueueAddService,
        BuildingQueueResolver,
        IsBuildingIdProvidedConstraint,
    ],
    imports: [
        BuildingZoneModule,
        BuildingModule,
        DatabaseModule,
        ConfigModule,
        AuthModule,
    ],
    exports: [
    ]
})
export class BuildingQueueModule {
}

4. 应用入口 (main.ts)

// main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from '@warp-core/app.module';
import { useContainer } from 'class-validator';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
    }),
  );

  useContainer(app.select(AppModule), { fallbackOnErrors: true });

  await app.listen(3000);
  console.log("App is loaded at " + (await app.getUrl()) + "/graphql URL");
}
bootstrap();

解决方案

1. 调整useContainer调用顺序

依赖注入失败的核心原因是**useContainer调用晚于ValidationPipe初始化**,class-validator需要先获取容器实例才能正确解析依赖。修改main.ts,将useContainer移到app.useGlobalPipes之前:

// main.ts 修改后
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from '@warp-core/app.module';
import { useContainer } from 'class-validator';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 先绑定容器,再初始化验证管道
  useContainer(app.select(AppModule), { fallbackOnErrors: true });

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
    }),
  );

  await app.listen(3000);
  console.log("App is loaded at " + (await app.getUrl()) + "/graphql URL");
}
bootstrap();

2. 确保服务被正确导出

检查BuildingZoneModule是否导出了BuildingZoneService,否则即使导入模块,其他模块也无法注入该服务:

// building-zone.module.ts 修改后
@Module({
  // 其他配置...
  exports: [BuildingZoneService],
})
export class BuildingZoneModule {}

完成以上两项修改后,自定义验证器中的buildingZoneService即可正常注入。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 20:38:12