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.ts
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
useGlobalPipes()
方法不会为网关和微服务设置管道(正在使用混合应用程序功能)
全局管道用于整个应用程序、每个控制器和每个路由处理程序。就依赖注入而言,从任何模块外部注册的全局管道(如上例所示)无法注入依赖,因为它们不属于任何模块。为了解决这个问题,可以使用以下构造直接为任何模块设置管道
//app.module.ts
import { 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.ts
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
readonly name: string;
@IsInt()
readonly age: number;
@IsString()
readonly breed: string;
}
//validation.pipe.ts
import { 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.ts
import { 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等相同,这意味着它们可以通过构造函数注入依赖项。