Nestjs-Validation参数校验器

1,807 阅读4分钟
前沿

为了保证服务端的稳定性和安全性,对前端发送的数据进行校验,是必不可少的。在Nest框架中,提供了管道来做这项工作,管道有两种类型:

  • 转换:将输入数据转换为所需的形式。
  • 验证:评估输入数据是否有效,如果有效则通过;否则,为错误数据抛出异常。
Validation

为了自动验证传入的请求数据,Nest提供了几个内置管道:

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe

ValidationPipe使用了强大的class-validator包,以及它的声明性验证装饰器。为我们提供了一种方便的使用方式来为所有传入的请求数据执行验证规则,我们只需要做一些简单的注释声明就可以。接下来,就让我们具体实践一下。

全局启用Validation

ValidationPipe是从@nestjs/common包中导出的。

main.ts中,设置全局管道:

import { NestFactory } from '@nestjs/core';
import { ValidationPipe, HttpStatus, BadRequestException } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 设置全局接口数据校验
  // 以下参数均可根据自身需求设置
  app.useGlobalPipes(new ValidationPipe({
    transform: true, // 启用参数类型自动转换,常规设置
    whitelist: true, // 监听参数白名单,常规设置
    // 禁止非白名单参数,存在非白名单属性报错。此项可根据需求而定,如果设置false,将剥离非白名单属性
    forbidNonWhitelisted: true, 
    // 设置校验失败后返回的http状态码
    errorHttpStatusCode: HttpStatus.BAD_REQUEST, 
    // 设置校验失败后的响应数据格式
    exceptionFactory: (errors) => {
      // 此处要注意,errors是一个对象数组,包含了当前所调接口里,所有验证失败的参数及错误信息。
      // 此处的处理是只返回第一个错误信息
      let msg = Object.values(errors[0].constraints)[0];
      return new BadRequestException({
         msg: msg,
         status: HttpStatus.BAD_REQUEST
      })
    }
  }));
  await app.listen(3000);
}
bootstrap();

ValidationPipe这个管道背后使用的是class-validator class-transformer,所以如果有其他配置需求,可以查看对应的文档Nestjs-Validation文档。以下可做参考:

OptionTypeDescription
skipMissingPropertiesboolean如果设置为true,验证器会跳过未传参数的校验
whitelistboolean如果设置为true,验证器会剥离未设置验证装饰器的参数。
forbidNonWhitelistedboolean如果设置为true,验证器会对未设置验证装饰器的参数抛出异常,而不是剥离该属性。
forbidUnknownValuesboolean如果设置为true,对未知对象立即报错。
disableErrorMessagesboolean如果设置为true,验证错误信息将不会返回给客户端。
errorHttpStatusCodenumber设置指定的异常类型。默认会抛出BadRequestException。
exceptionFactoryFunction可以获取验证错误的数组,并返回要抛出的异常对象。
groupsstring[]在对象验证期间使用的组。
dismissDefaultMessagesboolean如果设置为true,验证器不使用默认消息。如果没有显式设置,错误消息就是undefined
validationError.targetboolean指示是否应该在ValidationError中公开目标
validationError.valueboolean指示验证值是否应在ValidationError中公开。
设置验证规则

首先,我们新建一个测试接口:

创建用户:Post http://test.com/v1/users

import { Post, Body, Controller } from '@nestjs/common';
import { SaveUserDto } from './dto/user.dto';

@Controller()
export class ManagerController {
    constructor() { }

    @Post('users')
    async save(@Body() body: SaveUserDto): Promise<any> {
      // 直接返回拿到的消息体,简单测试用
      return body;
    }
}

接下来就需要定义user.dto.ts文件,用来为接口传入的数据设置校验规则:

// class-validator 为我们提供了很对方便有用的校验装饰器,可查看 https://github.com/typestack/class-validator
// 选择自己需要的校验器
import { ValidateIf, IsNotEmpty, IsString, IsEmail, IsNumber, IsMobilePhone } from 'class-validator';

export class SaveUserDto {
    // 不能为空
    // 还可以传入一个配置参数,对校验过程进行定义
    @IsNotEmpty({ message: '用户名不能为空' })
    username: string;

    @IsNotEmpty({ message: '密码不能为空' })
    password: string;
	
    // 注意执行顺序,IsNotEmpty -> IsMobilePhone
    @IsMobilePhone('zh-CN')
    @IsNotEmpty({ message: '手机号不能为空' })
    mobile: string;

    // 这里的设置是可以将email设置成选填,当传入了email参数才会校验合法性
    @ValidateIf((target, value) => {
      	// 返回false表示取消此参数的校验器
        if (value === undefined) return false;
        return true;
    })
    @IsEmail({}, { message: 'email格式不正确' })
    email: string;

    // 这里设置了默认值-1,所以当没有传入此参数的时候,Controller收到的body中也会有:rid=-1
    @IsNotEmpty({ message: 'rid不能为空' })
    rid: number = -1;
}
局部使用Validation

局部使用跟使用管道、守卫方法一样,方法如下:

import { Post, Body, Controller, ValidationPipe, UsePipes } from '@nestjs/common';
import { SaveUserDto } from './dto/user.dto';

@Controller()
export class ManagerController {
    constructor() { }
	
    @UsePipes(new ValidationPipe())
    @Post('users')
    async save(@Body() body: SaveUserDto): Promise<any> {
      // 直接返回拿到的消息体,简单测试用
      return body;
    }
}
测试

如果验证失败,将返回对应错误信息

{
  "status": 400,
  "msg": "用户名不能为空"
}
总结

Validation用法很简单,但在实际项目业务中,会遇到很多复杂的校验规则和流程。class-validator 功能很强大,封装了很多实用的校验器,这可能就需要我们多了解文档,灵活搭配不同的校验器以适应我们的业务需求。