前言
本项目使用的数据验证工具并非midwayjs官方提供的,而是使用class-validator。我也是刚接触这个工具不久,所以这篇文章算是我对这个工具整理的一些用法和心得。
class-validator官方文档提供了很多验证器,并且在文档底部将所有的验证器都列举出来了。如果觉得看官方文档入门比较困难的话,可以先看这篇帖子。简单学会如何使用后,可以根据需要在文档底部的验证器表格中找到自己想要的验证器,如果不满足需求,可以自己定制验证器
自定义验证器
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';//对比所在对象的指定属性的长度export function IsLongerThan(property: string, validationOptions?: ValidationOptions) {return function (object: Object, propertyName: string) {registerDecorator({name: 'isLongerThan',//验证器名称target: object.constructor,//目标propertyName: propertyName,//属性名constraints: [property],//约束条件options: validationOptions,//验证器执行参数validator: {validate(value: any, args: ValidationArguments) {const [relatedPropertyName] = args.constraints;const relatedValue = (args.object as any)[relatedPropertyName];return typeof value === 'string' && typeof relatedValue === 'string' && value.length > relatedValue.length; // you can return a Promise<boolean> here as well, if you want to make async validation},},});};}class MyClass {//将会拿name值和label值的长度进行比较@IsLongerThan('label', { message: '$property长度小于$constraint1' })name: string;label: string;}const model = new MyClass();model.name = 'hello world';model.label = 'hello';return validator.validate(model, { forbidUnknownValues: true }).then(errors => {console.log('errors', errors);});
验证分组
在定义DTO的时候,创建时不需要进行唯一标识的必填验证,但修改是需要判断的,其他的参数验证跟创建DTO一样。当然你也可以为不同场景创建多个DTO,但这样会增加维护成本,比如新增某些字段,你就得对去修改多个DTO代码文件。
那么有没有一种办法可以只维护一个DTO,然后根据不同场景决定使用哪些验证器。办法当然是有的。
class UserDTO {
@IsNotEmpty({groups:['put']})
id: string;
@IsNotEmpty({groups:['post','put']})
username: string;
@IsNotEmpty({groups:['post','put']})
password:string;
}
const user = new UserDTO();
user.username='123456';
user.password='123456';
//只对分组post进行验证,也就是不会使用id属性必填验证器
return validator.validate(model, {groups:['post']}).then(errors => {
console.log('errors', errors);
});
验证器执行参数整理
- groups 对验证器进行分组
- forbidUnknownValues 翻译为禁止未知值,这个设置值比较令人迷惑,经过测试得出一个结论:执行验证时,如果forbidUnknownValues 设置为true并且目标对象找不到任何验证器,验证将无法通过,并提示:”unknownValue”:”an unknown value was passed to the validate function”
- strictGroups 设置为true,将按照严格按照分组来使用验证器。
- always 设置为true 验证器将不受分组的限制,始终会执行
- 关于验证器执行参数还有其他设置,请自行探索,以上列举的是我目前常用的几个参数 ```typescript //============ forbidUnknownValues:true start ================= class UserDTO { username: string; password:string; }
const user = new UserDTO(); user.username=’123456’; user.password=’123456’; return validator.validate(user).then(errors => { console.log(‘errors’, errors); //“unknownValue”:”an unknown value was passed to the validate function” });
return validator.validate({name:”123”}).then(errors => { console.log(‘errors’, errors); //“unknownValue”:”an unknown value was passed to the validate function” });
class UserDTO { @IsNotEmpty({groups:[‘put’]}) id: string; @IsNotEmpty({groups:[‘post’,’put’]}) username: string; @IsNotEmpty({groups:[‘post’,’put’]}) password:string; } const user = new UserDTO(); user.username=’123456’; user.password=’123456’; //指定不存在的分组验证器,由于无任何验证器可以使用,所以也无法通过unknownValue验证器 return validator.validate(user, {groups:[‘delete’]}).then(errors => { console.log(‘errors’, errors); //“unknownValue”:”an unknown value was passed to the validate function” }); //============ forbidUnknownValues:true end =================
//================== strictGroups:true start ================== class UserDTO { @IsNotEmpty({groups:[‘put’]}) id: string; @IsNotEmpty() username: string; @IsNotEmpty() password:string; } const user = new UserDTO(); user.username=’123456’; user.password=’123456’; //打算只验证username和password return validator.validate(user).then(errors => { console.log(‘errors’, errors); //不符合预期:id验证器验证不通过 }); return validator.validate(user,{strictGroups:true}).then(errors => { console.log(‘errors’, errors); //符合预期:验证通过,只验证username和password }); //================== strictGroups:true end==================
//==================== always:true start ==================== class UserDTO { @IsNotEmpty({groups:[‘put’]}) id: string; @IsNotEmpty() username: string; @IsNotEmpty() password:string; } const user = new UserDTO(); user.id=’001’; //除了验证id以外,还要验证username和password return validator.validate(user,{groups:[‘put’]}).then(errors => { console.log(‘errors’, errors); //验证通过,不符合预期:除了验证id以外,还要验证username和password });
//除了验证id以外,还要验证username和password return validator.validate(user,{groups:[‘put’],always:true}).then(errors => { console.log(‘errors’, errors); //验证不通过,符合预期:验证id、username和password });
通过以上的几个例子,我们执行参数将会使用以下设置
```typescript
//验证对象至少拥有一个验证器、按照严格的分组模式使用验证器(即分组传空,只执行没有分组的),如果使用分组验证器,希望同时验证没有分组的验证器
return validator.validate(user,{groups:['put'],always:true,strictGroups:true,forbidUnknownValues:true}).then(errors => {
console.log('errors', errors);
});
嵌套对象的验证
class-transfomer提供了@Type装饰器指定属性类型,用于调用转换方法时对嵌套对象的内部属性进行赋值
class-validator提供了@ValidateNested装饰器用于告知class-validator某个嵌套对象需要进行验证
import { Expose, plainToClass, Type } from 'class-transformer';
import { IsNotEmpty, validate, ValidateNested } from 'class-validator';
class UserInfo {
@Expose()
@IsNotEmpty()
userId: string;
@Expose()
name: string; //姓名
@Expose()
age: number; //年龄
@Expose()
sex: string; //性别
}
class AddressInfo {
@Expose()
province: string; //省
@Expose()
city: string; //市
@Expose()
district: string; //区
@Expose()
address: string; //详细地址
}
class EnterpriseInfo {
@Expose()
@IsNotEmpty()
enterpriseId: string; //企业Id
@Expose()
enterpriseName: string; //企业名称
@Expose()
contactPerson: string; //联系人
@Expose()
contactPhone: string; //联系人电话
@Type(() => AddressInfo)
@Expose()
addressInfo: AddressInfo;
}
export class CreateBindingEnterpriseApplicationCommand {
@Type(() => UserInfo)
@Expose()
@ValidateNested() //验证嵌套对象
userInfo: UserInfo;
@Type(() => EnterpriseInfo)
@Expose()
@ValidateNested() //验证嵌套对象
enterpriseInfo: EnterpriseInfo;
}
const main = async () => {
const json = {
//用户信息快照
userInfo: {
userId: '123', //用户Id
name: '张三', //姓名
age: 18, //年龄
sex: '0' //性别
},
//企业信息快照
enterpriseInfo: {
enterpriseId: '', //企业Id
enterpriseName: '测试企业', //企业名称
contactPerson: '李四', //联系人
contactPhone: '12345678900', //联系人电话
//企业地址快照
addressInfo: {
province: '北京市', //省
city: '市辖区', //市
district: '潮阳区', //区
address: '苏宁广场' //详细地址
}
}
};
const entity = plainToClass(CreateBindingEnterpriseApplicationCommand, json, {
excludeExtraneousValues: true,
exposeDefaultValues: true
});
return await validate(entity, {
forbidUnknownValues: true,
always: true,
strictGroups: true
});
};
describe('test/validate.test.ts', () => {
it('测试嵌套对象的转换验证', async () => {
const errors = await main();
console.log(JSON.stringify(errors));
//[{"target":{"userInfo":{"userId":"123","name":"张三","age":18,"sex":"0"},"enterpriseInfo":{"enterpriseId":"","enterpriseName":"测试企业","contactPerson":"李四","contactPhone":"12345678900","addressInfo":{"province":"北京市","city":"市辖区","district":"潮阳区","address":"苏宁广场"}}},"value":{"enterpriseId":"","enterpriseName":"测试企业","contactPerson":"李四","contactPhone":"12345678900","addressInfo":{"province":"北京市","city":"市辖区","district":"潮阳区","address":"苏宁广场"}},"property":"enterpriseInfo","children":[{"target":{"enterpriseId":"","enterpriseName":"测试企业","contactPerson":"李四","contactPhone":"12345678900","addressInfo":{"province":"北京市","city":"市辖区","district":"潮阳区","address":"苏宁广场"}},"value":"","property":"enterpriseId","children":[],"constraints":{"isNotEmpty":"enterpriseId should not be empty"}}]}]
expect(errors.length).toBeGreaterThan(0); //断言验证错误数量大于0
});
});
