我们会用 class-validator 的装饰器对 dto 对象做校验。
那 class-validator 都有哪些装饰器可用呢?
这节我们来过一遍。
nest new class-validator-decorators
创建个 CRUD 模块:
nest g resource aaa --no-spec
全局启用 ValidationPipe,对 dto 做校验:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
安装用到的 class-validator 和 class-transformer 包:
npm install --save class-validator class-transformer
然后在 create-aaa.dto.ts 加一下校验:
import { IsEmail, IsNotEmpty, IsString } from "class-validator";
export class CreateAaaDto {
@IsNotEmpty({message: 'aaa 不能为空'})
@IsString({message: 'aaa 必须是字符串'})
@IsEmail({}, {message: 'aaa 必须是邮箱'})
aaa: string;
}
把服务跑起来:
npm run start:dev
postman 里访问下:
这就是 class-validator 的装饰器的用法。
类似这种装饰器有很多。
和 @IsNotEmpty 相反的是 @IsOptional:
加上之后就是可选的了:
上节学的 PartialType 就是用的 IsOptional 装饰器实现的。
@IsIn 可以限制属性只能是某些值:
@IsNotEmpty({message: 'aaa 不能为空'})
@IsString({message: 'aaa 必须是字符串'})
@IsEmail({}, {message: 'aaa 必须是邮箱'})
@IsIn(['aaa@aa.com', 'bbb@bb.com'])
aaa: string;
还有 @IsNotIn,可以限制属性不能是某些值:
@IsNotEmpty({message: 'aaa 不能为空'})
@IsString({message: 'aaa 必须是字符串'})
@IsEmail({}, {message: 'aaa 必须是邮箱'})
@IsNotIn(['aaa@aa.com', 'bbb@bb.com'])
aaa: string;
@IsBoolean、@IsInt、@IsNumber、@IsDate 这种就不说了。
@IsArray 可以限制属性是 array:
@IsArray()
bbb:string;
@ArrayContains 指定数组里必须包含的值:
@IsArray()
@ArrayContains(['aaa'])
bbb:string;
类似的还有 @ArrayNotContains 就是必须不包含的值。
@ArrayMinSize 和 @ArrayMaxSize 限制数组的长度。
@ArrayUnique 限制数组元素必须唯一:
@IsArray()
@ArrayNotContains(['aaa'])
@ArrayMinSize(2)
@ArrayMaxSize(5)
@ArrayUnique()
bbb:string;
前面讲过 @IsNotEmpty,和它类似的还有 @IsDefined。
@IsNotEmpty 检查值是不是 ''、undefined、null。
@IsDefined 检查值是不是 undefined、null。
当你允许传空字符串的时候就可以用 @IsDefined。
@IsDefined()
ccc: string;
如果是 @IsNotEmpty,那空字符串也是不行的:
// @IsDefined()
@IsNotEmpty()
ccc: string;
数字可以做更精准的校验:
@IsPositive()
@Min(1)
@Max(10)
@IsDivisibleBy(2)
ddd:number;
@IsPositive 是必须是正数、@IsNegative 是必须是负数。
@Min、@Max 是限制范围。
@IsDivisibleBy 是必须被某个数整除。
@IsDateString 是 ISO 标准的日期字符串:
@IsDateString()
eee: string;
也就是这种:
还有几个字符串相关的:
@IsAlpha 检查是否只有字母
@IsAlphanumeric 检查是否只有字母和数字
@Contains 是否包含某个值
@IsAlphanumeric()
@Contains('aaa')
fff: string;
字符串可以通过 @MinLength、@MaxLength、@Length 来限制长度:
@MinLength(2)
@MaxLength(6)
ggg: string;
也可以用 @Length:
@Length(2, 6)
ggg: string;
还可以校验颜色值的格式:@IsHexColor、@IsHSL、@IsRgbColor
校验 IP 的格式:@IsIP
校验端口: @IsPort
校验 JSON 格式 @IsJSON
常用的差不多就这些,更多的可以看 class-validator 的文档。
此外,如果某个属性是否校验要根据别的属性的值呢?
这样:
@IsBoolean()
hhh: boolean;
@ValidateIf(o => o.hhh === true)
@IsNotEmpty()
@IsHexColor()
iii: string;
如果 hhh 传了 true,那就需要对 iii 做校验,否则不需要。
此外,如果这些内置的校验规则都不满足需求呢?
那就自己写!
创建 my-validator.ts
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
@ValidatorConstraint()
export class MyValidator implements ValidatorConstraintInterface {
validate(text: string, validationArguments: ValidationArguments) {
console.log(text, validationArguments)
return true;
}
}
用 @ValidatorConstraint 声明 class 为校验规则,然后实现 ValidatorConstraintInterface 接口。
用一下:
@Validate(MyValidator, [11, 22], {
message: 'jjj 校验失败',
})
jjj: string;
访问下:
第一个参数传入的字段值,第二个参数包含更多信息,比如 @Validate 指定的参数在 constraints 数组里。
这样,我们只要用这些做下校验然后返回 true、false 就好了。
比如这样:
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
@ValidatorConstraint()
export class MyValidator implements ValidatorConstraintInterface {
validate(text: string, validationArguments: ValidationArguments) {
// console.log(text, validationArguments)
return text.includes(validationArguments.constraints[0]);
}
}
内容包含 11 的时候才会校验通过。
那如果这个校验是异步的呢?
返回 promise 就行:
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
@ValidatorConstraint()
export class MyValidator implements ValidatorConstraintInterface {
async validate(text: string, validationArguments: ValidationArguments) {
// console.log(text, validationArguments)
return new Promise<boolean>((resolve) => {
setTimeout(() => {
resolve(text.includes(validationArguments.constraints[0]));
}, 3000);
})
}
}
这样用起来还是不如内置装饰器简单:
可以用我们前面学的创建自定义装饰器的方式来包装一下:
创建 my-contains.decorator.ts
import { applyDecorators } from '@nestjs/common';
import { Validate, ValidationOptions } from 'class-validator';
import { MyValidator } from './my-validator';
export function MyContains(content: string, options?: ValidationOptions) {
return applyDecorators(
Validate(MyValidator, [content], options)
)
}
用 applyDecorators 组合装饰器生成新的装饰器。
然后用起来就可以这样:
@MyContains('111', {
message: 'jjj 必须包含 111'
})
jjj: string;
我们封装出了 @Contains,其实内置的那些装饰器我们都可以自己封装出来。
案例代码在小册仓库
总结
我们过了一遍 class-validator 的常用装饰器。
它们可以对各种类型的数据做精确的校验。
然后 @ValidateIf 可以根据别的字段来决定是否校验当前字段。
如果内置的装饰器不符合需求,完全可以自己实现,然后用 @Validate 来应用,用自定义装饰器 applyDecorators 包一层之后,和 class-validator 的内置装饰器就一模一样了。
所有的 class-validator 内置装饰器我们完全可以自己实现一遍。