https://nestjs.com/
https://www.jianshu.com/p/eec0586409da
中文文档
https://docs.nestjs.cn
管道pipes
管道范围
- 参数范围
//cats.controler.ts@Post()async create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) {this.catsService.create(createCatDto);}
2. 方法范围
//cats.controler.ts@Post()@UsePipes(new ValidationPipe())async create(@Body() createCatDto: CreateCatDto) {this.catsService.create(createCatDto);}//@UsePipes() 修饰器是从 @nestjs/common 包中导入的
- 控制器范围
- 全局范围
//main.tsasync function bootstrap() {const app = await NestFactory.create(ApplicationModule);app.useGlobalPipes(new ValidationPipe());await app.listen(3000);}bootstrap();
useGlobalPipes() 方法不会为网关和微服务设置管道(正在使用混合应用程序功能)
全局管道用于整个应用程序、每个控制器和每个路由处理程序。就依赖注入而言,从任何模块外部注册的全局管道(如上例所示)无法注入依赖,因为它们不属于任何模块。为了解决这个问题,可以使用以下构造直接为任何模块设置管道
//app.module.tsimport { Module } from '@nestjs/common';import { APP_PIPE } from '@nestjs/core';@Module({providers: [{provide: APP_PIPE,useClass: CustomGlobalPipe,},],})export class ApplicationModule {}//上述6.0官方的示例代码目前是错误的,我们使用了5.0的示例代码。
验证的分类
- 路由处理程序(打破单个责任原则(SRP))
- 验证器类(每次在方法开始的时候我们都必须使用这个验证器)
//cats.controler.ts//依赖注入@Post()@UsePipes(ValidationPipe)async create(@Body() createCatDto: CreateCatDto) {this.catsService.create(createCatDto);}
- 验证中间件(但不可能创建一个通用的中间件,可以在整个应用程序中使用)
- 管道验证(最佳)
验证管道
对象结构验证
常用方法之一是使用基于结构的验证。Joi库是一个工具,它允许您使用一个可读的API以非常简单的方式创建结构。为了创建一个使用对象结构的管道,我们需要创建一个简单的类,该类将结构作为constructor参数
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;}}
管道绑定非常简单-我们需要使用 @UsePipes() 修饰器并使用有效的Joi结构创建管道实例
@Post()@UsePipes(new JoiValidationPipe(createCatSchema))async create(@Body() createCatDto: CreateCatDto) {this.catsService.create(createCatDto);}
类验证器
Nest 与 class-validator 配合得很好。这个优秀的库允许您使用基于装饰器的验证。基于装饰器的验证对于管道功能非常强大,因为我们可以访问已处理属性的 metatype。在我们开始之前,我们需要安装所需的软件包
npm i --save class-validator class-transformer
//create-cat.dto.tsimport { IsString, IsInt } from 'class-validator';export class CreateCatDto {@IsString()readonly name: string;@IsInt()readonly age: number;@IsString()readonly breed: string;}
//validation.pipe.tsimport { PipeTransform, Pipe, 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, metadata: ArgumentMetadata) {const { metatype } = metadata;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);}}
我们来看看这个代码。首先,请注意 transform() 函数是 异步 的。这是可能的,因为Nest支持同步和异步管道。另外,还有一个辅助函数 toValidate()。由于性能原因,它负责从验证过程中排除原生 JavaScript类型。最后一个重要的是我们必须返回相同的价值。这个管道是一个特定于验证的管道,所以我们需要返回完全相同的属性以避免重写(如前所述,管道将输入转换为所需的输出)。
最后一步是设置 ValidationPipe 。管道,与异常过滤器相同,它们可以是方法范围的、控制器范围的和全局范围的。另外,管道可以是参数范围的。我们可以直接将管道实例绑定到路由参数装饰器,例如@Body()。让我们来看看下面的例子
//cats.controler.ts@Post()async create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) {this.catsService.create(createCatDto);}
当验证逻辑仅涉及一个指定的参数时,参数范围的管道非常有用。要在方法级别设置管道,您需要使用 UsePipes() 装饰器
//cats.controler.ts@Post()@UsePipes(new ValidationPipe())async create(@Body() createCatDto: CreateCatDto) {this.catsService.create(createCatDto);}
转换管道
有时从客户端传来的数据需要经过一些修改。此外,有些部分可能会丢失,所以我们必须应用默认值。转换管道填补了客户端请求和请求处理程序之间的空白
//parse-int.pipe.tsimport { PipeTransform, Pipe, ArgumentMetadata, HttpStatus, BadRequestException } from '@nestjs/common';@Injectable()export class ParseIntPipe implements PipeTransform<string> {async transform(value: string, metadata: ArgumentMetadata) {const val = parseInt(value, 10);if (isNaN(val)) {throw new BadRequestException('Validation failed');}return val;}}
这是一个 ParseIntPipe,它负责将一个字符串解析为一个整数值。现在我们将管道绑定到选定的参数:
由于上述结构,ParseIntpipe 将在请求触发相应的处理程序之前执行。
另一个有用的例子是按 ID 从数据库中选择一个现有的用户实体
@Get(':id')findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {return userEntity;}
内置管道
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 中展示。
ValidationPipe 需要同时安装 class-validator 和 class-transformer 包
守卫guards
守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口
守卫有一个单独的责任。它们确定请求是否应该由路由处理程序处理。到目前为止,访问限制逻辑大多在中间件内。这样很好,因为诸如 token 验证或将 request 对象附加属性与特定路由没有强关联。
但中间件是非常笨的。它不知道调用 next() 函数后会执行哪个处理程序。另一方面,守卫可以访问 ExecutionContext 对象,所以我们确切知道将要执行什么。
守卫在每个中间件之后执行的,但在拦截器和管道之前。
守卫范围
- 控制器范围
- 方法范围
- 全局范围
授权守卫最好的守卫用例之一就是授权逻辑,因为只有当调用者具有足够的权限时才能使用特定的路由。我们计划创建的
AuthGuard将按顺序提取并验证请求标头中发送的 token。角色守卫
反射器
拦截器interceptors
拦截器是使用@Injectable()装饰器注解的类。拦截器应该实现NestInterceptor接口。
拦截器具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:
- 在函数执行之前/之后绑定额外的逻辑
- 转换从函数返回的结果
- 转换从函数抛出的异常
- 扩展基本函数行为
- 根据所选条件完全重写函数 (例如, 缓存目的)
拦截器的作用与控制器controllers,提供者providers,守卫guards等相同,这意味着它们可以通过构造函数注入依赖项。
