NestJs 中使用 验证功能

在NestJS 的 管道 学习文章中我们已经接触过 NestJs 中开箱即用的管道。而这篇文章主要是使用ValidationPipe实现更高级的定制功能。

使用内置的 ValidationPipe

因为内置的ValidationPipe管道内置使用了class-validatorclass-transformer所以我们首先需要安装这两个库。

$ npm i --save class-validator class-transformer

ValidationPipe依赖class-validatorclass-transformer库,所以当我们创建此管道后可以设置如下配置参数,来做一些管道初始化的操作:

选项 类型 描述
enableDebugMessages boolean 如果设置为 true,当出现问题时,验证器将向控制台打印额外的警告消息
skipUndefinedProperties boolean 如果设置为 true,则验证器将跳过验证对象中未定义的所有属性的验证
skipNullProperties boolean 如果设置为 true,则验证器将跳过验证对象中所有为 null 的属性的验证
skipMissingProperties boolean 如果设置为 true,则验证器将跳过验证对象中所有为 null 或未定义的属性的验证
whitelist boolean 如果设置为 true,验证器将删除已验证(返回)对象的任何不使用任何验证装饰器的属性
forbidNonWhitelisted boolean 如果设置为 true,验证器将抛出异常,而不是剥离非白名单属性
forbidUnknownValues boolean 如果设置为 true,尝试验证未知对象会立即失败
disableErrorMessages boolean 如果设置为 true,验证错误将不会返回给客户端
errorHttpStatusCode number 此设置允许您指定在发生错误时将使用哪种异常类型。默认情况下它会抛出 BadRequestException
exceptionFactory Function 获取验证错误数组并返回要抛出的异常对象
groups string[] 验证对象期间要使用的组
always boolean 设置 always 装饰器选项的默认值。可以在装饰器选项中覆盖默认值
strictGroups boolean 如果 groups 未给出或为空,则忽略至少一组的装饰器
dismissDefaultMessages boolean 如果设置为 true,验证将不会使用默认消息。undefined 如果没有明确设置,错误消息总是会出现
validationError.target boolean 指示目标是否应暴露在 ValidationError
validationError.value boolean 指示是否应在 中公开经过验证的值 ValidationError
stopAtFirstError boolean 当设置为 true 时,给定属性的验证将在遇到第一个错误后停止。默认为 false

自动验证

我们将从 ValidationPipe 应用程序级别的绑定开始,从而确保所有端点都免受接收错误数据的影响。具体代码如下:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

我们添加了全局的校验器后,我们就可以在控制器中使用校验器,具体实例如下:

// 校验类
export class CreateDemoDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsNotEmpty()
  password: string;

  @IsNumber()
  count: number;
}

// 控制器
@Post('/checkParams')
checkParams(@Body() createDemo: CreateDemoDto) {
  return 'This action adds a new user';
}

当我们的请求参数为:

{
  "name": "",
  "email": "",
  "password": "",
  "count": 1
}

接口返回结果如下:

{
  "statusCode": 400,
  "message": ["email must be an email", "password should not be empty"],
  "error": "Bad Request"
}

禁用详细错误

错误消息有助于解释请求中的错误内容。但是,某些生产环境更喜欢禁用详细错误。通过将选项对象传递给 ValidationPipe:

app.useGlobalPipes(
  new ValidationPipe({
    // 设置为true后,错误信息就不会在响应正文中显示
    disableErrorMessages: true,
  })
);

当我们发送上述的 JSON 请求参数后,接口返回的错误信息如下:

{
  "statusCode": 400,
  "message": "Bad Request"
}

剔除不需要检测的属性

ValidationPipe还可以过滤掉不必要的属性,主要我们在ValidationPipe中开启白名单就可以实现过滤属性功能。具体实例代码如下:

export class CreateDemoDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsNotEmpty()
  password: string;

  @IsNumber()
  count: number;
}

请求参数为:

{
  "name": "Tom",
  "email": "[email protected]",
  "password": "123456",
  "count": 1,
  "age": 12 // DTO中不存在该属性,参数接受不会显示该属性
}

上述我们只是开启了白名单,只是简单让不需要的属性过滤掉,我们还可以在存在非白名单属性时停止处理请求,并返回对应的错误信息,要开启此功能只要把forbidNonWhitelisted属性设置为 true 即可。具体实例如下:

app.useGlobalPipes(
  new ValidationPipe({
    disableErrorMessages: true,
    whitelist: true,
    forbidNonWhitelisted: true,
  })
);

请求对象转为具体的 DTO 对象

通过网络传入的有效负载是纯 JavaScript 对象。可以 ValidationPipe 自动将请求参数转为对应的 DTO 对象。要启动自动转换,只要设置transform属性为 true 即可。具体的代码如下:

// 方法级别开启转换
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

// 全局开启转换
app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
  }),
);

启用自动转换选项后,还将 ValidationPipe 执行基本类型的转换。在以下示例中,该 findOne()方法采用一个参数来表示提取的 id 路径参数,但是@Query()是不支持自动转换对象的:

@Get(':id')
findOne(@Param('id') id: number) {
  // 会自动把Id值转为数值
  console.log(typeof id === 'number'); // true
  return 'This action returns a user';
}

显示转换

在上面的例子中,我们开启了自动转换功能来转换对应的参数类型,我们还可以使用管道来帮我们转换数据类型。具体实例如下:

@Get(':id')
findOne(
  @Param('id', ParseIntPipe) id: number,
  @Query('sort', ParseBoolPipe) sort: boolean,
) {
  console.log(typeof id === 'number'); // true
  console.log(typeof sort === 'boolean'); // true
  return 'This action returns a user';
}

解析和验证数组

TypeScript 不存储有关泛型或接口的元数据,因此当您在 DTO 中使用它们时,ValidationPipe 可能无法正确验证传入数据。例如,在以下代码中,createUserDtos 将无法正确验证:

@Post()
createBulk(@Body() createUserDtos: CreateUserDto[]) {
  return 'This action adds new users';
}

要验证数组,请创建一个专用类,其中包含包装数组的属性,或使用 ParseArrayPipe。

@Post()
createBulk(
  @Body(new ParseArrayPipe({ items: CreateUserDto }))
  createUserDtos: CreateUserDto[],
) {
  return 'This action adds new users';
}

此外,ParseArrayPipe 在解析查询参数时可能会派上用场。让我们考虑一种 findByIds()根据作为查询参数传递的标识符返回用户的方法。

@Get()
findByIds(
  // 请求参数为/?ids=1,2,3,通过ParseArrayPipe可以把ids解析成数组
  @Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
  ids: number[],
) {
  return 'This action returns users by ids';
}

DTO 编写技巧

构建输入验证类型(也称为 DTO)时,在同一类型上构建创建和更新变体通常很有用。例如,创建变体可能需要所有字段,而更新变体可能使所有字段可选。

  • PartialType() 函数

在 NestJS 中提供了PartialType()的实用函数,使用此函数可以最大程度上减少样板代码和简化代码量。具体实例如下:

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

//
export class UpdateCatDto extends PartialType(CreateCatDto) {}

说明: PartialType 函数是需要安装@nestjs/mapped-types插件包。

  • PartialType() 函数

PartialType() 函数主要是在原先的类上选择需要继承的属性。具体实例如下:

export class CreateDemoDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsNotEmpty()
  password: string;

  @IsInt()
  age: number;
}

// 选择继承age、name属性
export class UpdateDemoAgeDto extends PickType(CreateDemoDto, [
  "age",
  "name",
] as const) {}
  • OmitType() 函数

OmitType() 函数主要是在原先的类上删除不需要的属性。具体实例如下:

// 在CreateDemoDto原先类的基础上剔除name属性
export class UpdateDemoExDto extends OmitType(CreateDemoDto, [
  "name",
] as const) {}
  • IntersectionType() 函数

IntersectionType() 函数两种类型组合成一种新类型(类)。具体实例如下:

export class AdditionalDemoInfo {
  color: string;
}

// 将CreateDemoDto类和AdditionalDemoInfo类合并成UpdateDemoDto,这样UpdateDemoDto就具有CreateDemoDto和AdditionalDemoInfo的所有属性
export class UpdateDemoDto extends IntersectionType(
  CreateDemoDto,
  AdditionalDemoInfo
) {}

你可能感兴趣的:(NestJS学习笔记,笔记,学习,NestJs)