NestJS中如何实现带数据库访问的通用唯一性验证器?
通用唯一值验证器实现方案
方案可行性分析
完全可行。通过装饰器传入实体类+TypeORM EntityManager的方式,能实现通用的唯一值校验,无需为每个实体单独编写验证器。核心问题是解决class-validator验证器的依赖注入问题——默认情况下class-validator不会自动使用NestJS的DI容器,需要手动关联。
具体实现步骤
- 关联class-validator与NestJS DI容器:在项目入口配置
useContainer,让class-validator能从NestJS容器中获取依赖。 - 改用TypeORM EntityManager:替换mysql2的Connection,使用TypeORM封装好的EntityManager,更安全且适配NestJS的DI。
- 修改装饰器接收实体类:直接传入实体类而非字符串,避免SQL注入风险,同时能直接通过EntityManager获取对应仓库。
- 注册验证器为Provider:将验证器类加入模块的providers数组,确保DI容器能实例化并注入依赖。
修改后的代码
验证器文件
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments, ValidationOptions, registerDecorator, } from 'class-validator'; import { Injectable } from '@nestjs/common'; import { EntityManager } from 'typeorm'; export function IsUnique(entity: Function, validationOptions?: ValidationOptions) { return (object: object, propertyName: string): void => { registerDecorator({ target: object.constructor, propertyName, options: validationOptions, constraints: [entity], validator: IsUniqueClass, }); }; } @Injectable() @ValidatorConstraint({ name: 'isUnique', async: true }) export class IsUniqueClass implements ValidatorConstraintInterface { constructor(private readonly entityManager: EntityManager) {} async validate(value: unknown, args: ValidationArguments): Promise<boolean> { const [entityClass] = args.constraints; const propertyName = args.property; // 查询是否存在重复值 const count = await this.entityManager .getRepository(entityClass) .count({ where: { [propertyName]: value } }); return count === 0; } defaultMessage(args: ValidationArguments): string { return `值 '${args.value as string}' 已存在`; } }
DTO使用示例
import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; import { IsUnique } from './path-to-validator'; import { ExerciseType } from './path-to-exercise-type.entity'; export class CreateExerciseTypeDto { @ApiProperty() @IsNotEmpty() @IsString() @IsUnique(ExerciseType, { message: '运动类型名称已存在' }) // 传入实体类,可自定义错误信息 name: string; }
项目入口配置(main.ts)
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { useContainer } from 'class-validator'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 关联class-validator与NestJS容器 useContainer(app.select(AppModule), { fallbackOnErrors: true }); await app.listen(3000); } bootstrap();
AppModule配置
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { IsUniqueClass } from './path-to-validator'; import { TrainingModule, ExerciseModule, ExerciseTypeModule } from './modules'; import { AppController, AppService } from './app'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: '127.0.0.1', port: 3306, username: 'root', password: '', database: 'databaseName', autoLoadEntities: true, synchronize: true, logging: ['error'], }), TrainingModule, ExerciseModule, ExerciseTypeModule, ], controllers: [AppController], providers: [AppService, IsUniqueClass], // 注册验证器为Provider }) export class AppModule {}
关键说明
- 避免SQL注入:使用实体类而非直接传入表名,TypeORM会自动处理表名和参数绑定,避免注入风险。
- 依赖注入生效:通过
@Injectable()装饰验证器,并在main.ts配置useContainer,确保EntityManager能被正确注入。 - 通用性:任意实体的字段都可以通过
@IsUnique(EntityClass)实现唯一值校验,无需重复编写验证逻辑。
内容的提问来源于stack exchange,提问作者Hadock
相关产品推荐
相关产品推荐

