前言

本项目使用的数据验证工具并非midwayjs官方提供的,而是使用class-validator。我也是刚接触这个工具不久,所以这篇文章算是我对这个工具整理的一些用法和心得。

class-validator官方文档提供了很多验证器,并且在文档底部将所有的验证器都列举出来了。如果觉得看官方文档入门比较困难的话,可以先看这篇帖子。简单学会如何使用后,可以根据需要在文档底部的验证器表格中找到自己想要的验证器,如果不满足需求,可以自己定制验证器

自定义验证器

  1. import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';
  2. //对比所在对象的指定属性的长度
  3. export function IsLongerThan(property: string, validationOptions?: ValidationOptions) {
  4. return function (object: Object, propertyName: string) {
  5. registerDecorator({
  6. name: 'isLongerThan',//验证器名称
  7. target: object.constructor,//目标
  8. propertyName: propertyName,//属性名
  9. constraints: [property],//约束条件
  10. options: validationOptions,//验证器执行参数
  11. validator: {
  12. validate(value: any, args: ValidationArguments) {
  13. const [relatedPropertyName] = args.constraints;
  14. const relatedValue = (args.object as any)[relatedPropertyName];
  15. 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
  16. },
  17. },
  18. });
  19. };
  20. }
  21. class MyClass {
  22. //将会拿name值和label值的长度进行比较
  23. @IsLongerThan('label', { message: '$property长度小于$constraint1' })
  24. name: string;
  25. label: string;
  26. }
  27. const model = new MyClass();
  28. model.name = 'hello world';
  29. model.label = 'hello';
  30. return validator.validate(model, { forbidUnknownValues: true }).then(errors => {
  31. console.log('errors', errors);
  32. });

验证分组

在定义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
  });
});