From 43d34250e2322ff87ed921109fa431d66ed16626 Mon Sep 17 00:00:00 2001 From: liuyi Date: Tue, 27 May 2025 23:29:14 +0800 Subject: [PATCH] add constraint --- .../constraints/tree.unique.constraint.ts | 75 ++++++++++++++++ .../tree.unique.exist.constraint.ts | 89 +++++++++++++++++++ .../constraints/unique.exist.constraint.ts | 5 +- src/modules/database/database.module.ts | 11 ++- 4 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 src/modules/core/constraints/tree.unique.constraint.ts create mode 100644 src/modules/core/constraints/tree.unique.exist.constraint.ts diff --git a/src/modules/core/constraints/tree.unique.constraint.ts b/src/modules/core/constraints/tree.unique.constraint.ts new file mode 100644 index 0000000..97d088e --- /dev/null +++ b/src/modules/core/constraints/tree.unique.constraint.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@nestjs/common'; +import { + ValidatorConstraint, + ValidatorConstraintInterface, + ValidationArguments, + registerDecorator, + ValidationOptions, +} from 'class-validator'; +import { merge, isNil } from 'lodash'; +import { DataSource, ObjectType } from 'typeorm'; + +type Condition = { + entity: ObjectType; + + property?: string; +}; + +@ValidatorConstraint({ name: 'treeDataUnique', async: true }) +@Injectable() +export class TreeUniqueConstraint implements ValidatorConstraintInterface { + constructor(private dataSource: DataSource) {} + + async validate(value: any, args: ValidationArguments) { + // 获取要验证的模型和字段 + const config: Omit = { + property: args.property, + }; + const condition = ('entity' in args.constraints[0] + ? merge(config, args.constraints[0]) + : { + ...config, + entity: args.constraints[0], + }) as unknown as Required; + if (!condition.entity) { + return false; + } + try { + // 查询是否存在数据,如果已经存在则验证失败 + const repo = this.dataSource.getRepository(condition.entity); + return isNil( + await repo.findOne({ where: { [condition.property]: value }, withDeleted: true }), + ); + } catch (err) { + // 如果数据库操作异常则验证失败 + return false; + } + } + + defaultMessage(args: ValidationArguments) { + const { entity, property } = args.constraints[0]; + const queryProperty = property ?? args.property; + if (!(args.object as any).getManager) { + return 'getManager function not been found!'; + } + if (!entity) { + return 'Model not been specified!'; + } + return `${queryProperty} of ${entity.name} must been unique!`; + } +} + +export function IsUnique( + params: ObjectType | Condition, + validationOptions?: ValidationOptions, +) { + return (object: Record, propertyName: string) => { + registerDecorator({ + target: object.constructor, + propertyName, + options: validationOptions, + constraints: [params], + validator: TreeUniqueConstraint, + }); + }; +} diff --git a/src/modules/core/constraints/tree.unique.exist.constraint.ts b/src/modules/core/constraints/tree.unique.exist.constraint.ts new file mode 100644 index 0000000..24741e4 --- /dev/null +++ b/src/modules/core/constraints/tree.unique.exist.constraint.ts @@ -0,0 +1,89 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; +import { isNil, merge } from 'lodash'; +import { DataSource, Not, ObjectType } from 'typeorm'; + +type Condition = { + entity: ObjectType; + + ignore?: string; + + ignoreKey?: string; + + property?: string; +}; + +@ValidatorConstraint({ name: 'treeDataUniqueExist', async: true }) +@Injectable() +export class TreeUniqueExistContraint implements ValidatorConstraintInterface { + constructor(private dataSource: DataSource) {} + + async validate(value: any, args: ValidationArguments) { + const config: Omit = { + ignore: 'id', + property: args.property, + }; + const condition = ('entity' in args.constraints[0] + ? merge(config, args.constraints[0]) + : { + ...config, + entity: args.constraints[0], + }) as unknown as Required; + if (!condition.entity) { + return false; + } + // 在传入的dto数据中获取需要忽略的字段的值 + const ignoreValue = (args.object as any)[ + isNil(condition.ignoreKey) ? condition.ignore : condition.ignoreKey + ]; + // 如果忽略字段不存在则验证失败 + if (ignoreValue === undefined) { + return false; + } + // 通过entity获取repository + const repo = this.dataSource.getRepository(condition.entity); + // 查询忽略字段之外的数据是否对queryProperty的值唯一 + return isNil( + await repo.findOne({ + where: { + [condition.property]: value, + [condition.ignore]: Not(ignoreValue), + }, + withDeleted: true, + }), + ); + } + + defaultMessage(args: ValidationArguments) { + const { entity, property } = args.constraints[0]; + const queryProperty = property ?? args.property; + if (!(args.object as any).getManager) { + return 'getManager function not been found!'; + } + if (!entity) { + return 'Model not been specified!'; + } + return `${queryProperty} of ${entity.name} must been unique!`; + } +} + +export function IsUniqueExist( + params: ObjectType | Condition, + validationOptions?: ValidationOptions, +) { + return (object: Record, propertyName: string) => { + registerDecorator({ + target: object.constructor, + propertyName, + options: validationOptions, + constraints: [params], + validator: TreeUniqueExistContraint, + }); + }; +} diff --git a/src/modules/core/constraints/unique.exist.constraint.ts b/src/modules/core/constraints/unique.exist.constraint.ts index 345ef5c..de98fd9 100644 --- a/src/modules/core/constraints/unique.exist.constraint.ts +++ b/src/modules/core/constraints/unique.exist.constraint.ts @@ -1,7 +1,9 @@ +import { Injectable } from '@nestjs/common'; import { registerDecorator, ValidationArguments, ValidationOptions, + ValidatorConstraint, ValidatorConstraintInterface, } from 'class-validator'; import { isNil, merge } from 'lodash'; @@ -16,7 +18,8 @@ type Condition = { property?: string; }; - +@Injectable() +@ValidatorConstraint({ name: 'dataUniqueExist', async: true }) export class UniqueExistConstraint implements ValidatorConstraintInterface { constructor(protected dataSource: DataSource) {} diff --git a/src/modules/database/database.module.ts b/src/modules/database/database.module.ts index 6b16230..a3389fe 100644 --- a/src/modules/database/database.module.ts +++ b/src/modules/database/database.module.ts @@ -6,7 +6,10 @@ import { DataSource, ObjectType } from 'typeorm'; import { CUSTOM_REPOSITORY_METADATA } from '@/modules/database/constants'; import { DataExistConstraint } from '../core/constraints/data.exist.constraint'; +import { TreeUniqueConstraint } from '../core/constraints/tree.unique.constraint'; +import { TreeUniqueExistContraint } from '../core/constraints/tree.unique.exist.constraint'; import { UniqueConstraint } from '../core/constraints/unique.constraint'; +import { UniqueExistConstraint } from '../core/constraints/unique.exist.constraint'; @Module({}) export class DatabaseModule { @@ -15,7 +18,13 @@ export class DatabaseModule { global: true, module: DatabaseModule, imports: [TypeOrmModule.forRoot(configRegister())], - providers: [DataExistConstraint, UniqueConstraint], + providers: [ + DataExistConstraint, + UniqueConstraint, + UniqueExistConstraint, + TreeUniqueConstraint, + TreeUniqueExistContraint, + ], }; } static forRepository>(