首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

管道

管道是具有@Injectable()装饰器注释的类。管道应实现PipeTransform接口。

管道有两个典型的用例:

  • 转换:将输入数据转换为所需的形式(例如,从字符串到整数)
  • 验证:评估输入数据,如果有效,只需将其原样传递即可;否则,当数据不正确时抛出异常

在这两种情况下,管道都在arguments控制器路由处理程序处理的过程中运行。Nest会在调用方法之前插入一个管道,并且管道会接收指定给该方法的参数并对其进行操作。那时将进行任何转换或验证操作,然后使用任何(可能)转换后的参数调用路由处理程序。

Nest附带了许多内置管道,您可以直接使用它们。您还可以构建自己的自定义管道。在本章中,我们将介绍内置管道,并展示如何将它们绑定到路由处理程序。然后,我们将研究几个定制管道,以显示如何从头开始构建一个。

提示:管道在例外区域内运行。这意味着,当Pipe引发异常时,它由异常层(全局异常过滤器和应用于当前上下文的所有异常过滤器)进行处理。鉴于以上所述,应该清楚的是,当在Pipe中引发异常时,随后将不执行任何控制器方法。这为您提供了一种最佳实践方法,用于验证从系统边界处的外部源进入应用程序的数据。

内置管道

Nest 自带六个开箱即用的管道,即

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe

他们从 @nestjs/common 包中导出。为了更好地理解它们是如何工作的,我们将从头开始构建它们。

我们从 ValidationPipe. 开始。 首先它只接受一个值并立即返回相同的值,其行为类似于一个标识函数。

它是什么样子的?

让我们从开始吧ValidationPipe。在开始时,它只接受一个值并立即返回相同的值,表现得像一个身份函数。

validation.pipe.ts

JS

代码语言:javascript
复制
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}

提示PipeTransform<T, R>是一个通用的界面,其中T表示一个类型的输入的value,而R所述的返回类型transform()方法。

每个管道都必须提供transform()方法。此方法有两个参数:

  • value
  • metadata

value是当前处理的参数(在路由处理方法接收之前),并且metadata是当前处理的方法参数的元数据。元数据对象具有以下属性:

代码语言:javascript
复制
export interface ArgumentMetadata {
  readonly type: 'body' | 'query' | 'param' | 'custom';
  readonly metatype?: new (...args) => any;
  readonly data?: string;
}

这些属性描述输入参数。

type

告诉我们该属性是正文@Body(),查询@Query(),参数@Param()还是自定义参数(在此处阅读更多内容)。

metatype

例如,属性的元类型String。这undefined两种,如果你省略函数签名的类型声明,或者你使用香草的JavaScript。

data

例如,传递给装饰器的字符串@Body('string')。这是undefined,如果你离开括号空。

警告TypeScript接口在转换过程中消失。因此,如果使用接口而不是类,则metatype值将等于Object

重点是什么?

让我们关注一段时间的create()方法CatsController

cats.controller.ts

代码语言:javascript
复制
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

有一个CreateCatDto身体参数:

创建-cat.dto.ts

代码语言:javascript
复制
export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

这个对象总是必须正确,因此我们必须验证这三个成员。我们可以在路由处理程序方法中完成它,但是我们打破了单一责任规则(SRP)。第二个想法是创建一个验证器类并将任务委托给那里,但是每次在每个方法的开头都要使用这个验证器。那么验证中间件呢?这是一个好主意,但是创建一个可以在整个应用程序中使用的通用中间件是不可能的。

这是第一个用例,当你应该考虑使用Pipe

对象模式验证

经常遇到的方法之一是使用基于模式的验证。该穰库是一个工具,它允许你在一个程序API一个非常简单的方式创建模式。为了创建一个使用对象模式的管道,我们需要创建一个以模式作为constructor参数的简单类。

JS

代码语言:javascript
复制
import * as Joi from 'joi';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class JoiValidationPipe implements PipeTransform {
  constructor(private readonly schema) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = Joi.validate(value, this.schema);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}

绑定验证管道

之前,我们看到了如何绑定转换管道(例如管道ParseIntPipe的其余部分Parse*)。

绑定验证管道也非常简单。

在这种情况下,我们要在方法调用级别绑定管道。在当前示例中,我们需要执行以下操作才能使用JoiValidationPipe

  1. 创建一个实例 JoiValidationPipe
  2. 在管道的类构造函数中传递上下文特定的Joi模式
  3. 将管道绑定到方法

我们使用@UsePipes()装饰器来做到这一点,如下所示:

JS

代码语言:javascript
复制
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

类验证器

警告:本节中的技术需要TypeScript,如果您的应用是使用原始JavaScript编写的,则这些技术不可用。

让我们看看我们的验证技术的另一种实现。

Nest可与类验证器库一起很好地工作。这个功能强大的库使您可以使用基于装饰器的验证。基于装饰器的验证功能非常强大,尤其是在与Nest的Pipe功能结合使用时,因为我们可以访问metatype已处理属性的。在开始之前,我们需要安装所需的软件包:

代码语言:javascript
复制
$ npm i --save class-validator class-transformer

一旦安装了这些,我们就可以在CreateCatDto类中添加一些装饰器。在这里,我们看到了这项技术的显着优势:CreateCatDto该类仍然是Post正文对象的唯一真相来源(而不是必须创建单独的验证类)。

创建-cat.dto.ts

代码语言:javascript
复制
import { IsString, IsInt } from 'class-validator';

export class CreateCatDto {
  @IsString()
  readonly name: string;

  @IsInt()
  readonly age: number;

  @IsString()
  readonly breed: string;
}

完成后,我们可以创建一个ValidationPipe类。

validation.pipe.ts

代码语言:javascript
复制
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype): boolean {
    const types = [String, Boolean, Number, Array, Object];
    return !types.find((type) => metatype === type);
  }
}

注意:上面,我们使用了class-transformer库。它是由与类验证器库相同的作者制作的,因此,它们可以很好地协同工作。

让我们看一下这段代码。首先,请注意该transform()方法被标记为async。这是可能的,因为Nest支持同步管道和异步管道。我们之所以使用此方法,async是因为某些类验证器验证可以是异步的(利用Promises)。

接下来请注意,我们正在使用解构将元类型字段(仅从中提取此成员ArgumentMetadata)提取到我们的metatype参数中。这只是获取完整内容ArgumentMetadata然后具有附加语句来分配元类型变量的简写。

接下来,请注意辅助函数toValidate()。当正在处理的当前参数是本机JavaScript类型时(它没有附加验证装饰器,因此没有理由在验证步骤中运行它们),它负责绕过验证步骤。

接下来,我们使用class-transformer函数plainToClass()将普通的JavaScript参数对象转换为类型对象,以便我们可以应用验证。我们必须这样做的原因是,从网络请求中反序列化传入的帖子主体对象时,它没有任何类型信息(这是基础平台(例如Express)的工作方式)。类验证器需要使用之前为DTO定义的验证装饰器,因此我们需要执行此转换,以将传入的主体视为经过适当装饰的对象,而不仅仅是普通的香草对象。

最后,如前所述,由于这是一个验证管道,因此它要么返回值不变,要么抛出异常。

最后一步是设置ValidationPipe。与异常过滤器相同的管道可以是方法范围,控制器范围和全局范围。另外,管道可以是param-scoped。我们可以直接将管道实例绑定到路径参数装饰器,例如,@Body()装饰器。我们来看看下面的例子:

cats.controller.ts

代码语言:javascript
复制
@Post()
async create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

当验证逻辑仅涉及一个指定参数时,param-scoped管道很有用。要在方法级别设置管道,您需要UsePipes()装饰器。

cats.controller.ts

代码语言:javascript
复制
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

提示@UsePipes()装饰器从导入的@nestjs/common包。

该实例ValidationPipe已立即就地创建。另一种可用的方法是传递类(而不是实例),使框架具有实例化责任并启用依赖注入

cats.controller.ts

代码语言:javascript
复制
@Post()
@UsePipes(ValidationPipe)
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

由于ValidationPipe创建的内容尽可能通用,我们将把它设置为全局范围的管道,用于整个应用程序中的每个路由处理程序。

main.ts

JS

代码语言:javascript
复制
async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

注意useGlobalPipes()方法不为网关和微服务设置管道。

对于每个控制器和每个路由处理程序,全局管道在整个应用程序中使用。在依赖注入方面,从任何模块外部注册的全局管道(如上例中所示)不能注入依赖项,因为它们不属于任何模块。为了解决此问题,您可以使用以下构造直接从任何模块设置管道:

app.module.ts

JS

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: CustomGlobalPipe,
    },
  ],
})
export class ApplicationModule {}

提示替代选项是使用执行上下文功能。此外,useClass这不是处理自定义提供程序注册的唯一方法。在这里了解更多。

变压器管

验证不是唯一的用例。在本章开头,我们已经提到管道也可以输入数据转换为所需的输出。这是真的,因为从transform函数返回的值完全覆盖了参数的先前值。有时,从客户端传递的数据需要进行一些更改。此外,某些部分可能会遗漏,因此我们必须应用默认值。所述变压器管填充客户端的请求,并且该请求处理程序之间的差距。

解析-int.pipe.ts

JS

代码语言:javascript
复制
import { PipeTransform, Injectable, ArgumentMetadata, HttpStatus, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

这是一个ParseIntPipe负责将字符串解析为整数值的人。我们可以简单地将管道绑定到选定的参数:

JS

代码语言:javascript
复制
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}

由于上面的结构,ParseIntPipe将在请求之前执行甚至触及相应的处理程序。

另一个有用的例子是通过id从数据库中选择现有的用户实体:

JS

代码语言:javascript
复制
@Get(':id')
findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {
  return userEntity;
}

内置的ValidationPipe

幸运的是,你没有建立你自己的管道,因为ValidationPipeParseIntPipe是内置管道(记住,ValidationPipe既需要class-validatorclass-transformer安装的软件包)。

内置ValidationPipe提供了比本章所述更多的选项,为简单起见保留了基本内容并减少了学习曲线。如果您查看createCatDto控制器功能,您会发现它不是一个实际的CreateCatDto实例。这是因为此管道仅验证有效负载,而不将其转换为预期类型。但是,如果您希望管道改变有效负载,可以通过传递适当的选项来配置它:

cats.controller.ts

代码语言:javascript
复制
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

因为这个管道基于class-validatorclass-transformer库,所以可以获得更多。看看构造函数的可选选项。

代码语言:javascript
复制
export interface ValidationPipeOptions extends ValidatorOptions {
  transform?: boolean;
}

有一个transform属性和所有class-validator选项(继承自ValidatorOptions接口):

选项

类型

描述

skipMissingProperties

boolean

如果设置为true,则验证程序将跳过验证对象中缺少的所有属性的验证。

whitelist

boolean

如果设置为true,验证器将剥离任何不使用任何装饰器的属性的验证对象。

forbidNonWhitelisted

boolean

如果设置为true,则验证程序将抛出异??常,而不是剥离非白名单属性。

forbidUnknownValues

boolean

如果设置为true,则未知对象的验证将立即失败。

disableErrorMessages

boolean

如果设置为true,则验证错误不会转发到客户端。

groups

string[]

在验证对象期间要使用的组。

dismissDefaultMessages

boolean

如果设置为true,则验证将不使用默认消息。undefined如果未明确设置,则始终为错误消息。

validationError.target

boolean

指示是否应该公开目标?ValidationError

validationError.value

boolean

指示是否应公开验证值ValidationError。

注意您可以class-validator在其存储库中找到有关该程序包的更多信息。

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com