NestJS 的 管道 学习

news/2024/5/18 12:49:57

管道是一个用@Injectable()装饰器注释的类,它实现了 PipeTransform 接口。

管道的作用如下:

  • 转换:将输入数据转换为所需的形式(例如,从字符串到整数)
  • 验证:评估输入数据,如果有效,则简单地通过不变;否则,抛出异常

NestJs 在调用方法之前插入一个管道,该管道接收指定给该方法的参数并对它们进行操作。任何转换或验证操作都会在此时发生,之后使用任何(可能)转换的参数调用路由处理程序。转换和验证都是由控制器中进行处理的。

Nest 附带了许多内置管道,您可以开箱即用。您还可以构建自己的自定义管道。

注意:当管道中有异常发生时,随后不会执行任何控制的方法。

内置管道

管道说明
ValidationPipe验证管道,可以验证请求的数据是否符合要求
ParseIntPipe参数转换 init 类型的管道
ParseFloatPipe参数转换 float 类型的管道
ParseBoolPipe参数转换 布尔 类型的管道
ParseArrayPipe参数转换 数组 类型管道
ParseUUIDPipe参数转换 UUID 的管道
ParseEnumPipe参数转换 枚举值 的管道
DefaultValuePipe设置默认值的管道
ParseFilePipe解析文件流的管道

绑定管道

要使用管道的时候,我们主要将管道的实例绑定在适当的上下文上就可以。具体实例如下:

@Get('/:id')
getCommodityById(@Param('id', ParseIntPipe) id: number) {console.log(id)return this.commodityService.findById(id)
}

当请求的参数为非数字的字符串时,接口会直接抛出异常,具体情况如下:

{"statusCode": 400,"message": "Validation failed (numeric string is expected)","error": "Bad Request"
}

异常阻止了后续的业务处理(findById()方法的执行)。

在上面的示例中,我们传递一个类 ( ParseIntPipe),而不是一个实例,将实例化的责任留给框架并启用依赖项注入。与管道和守卫一样,我们可以传递一个就地实例。如果我们想通过传递选项来自定义内置管道的行为,则传递就地实例非常有用:

@Get('/:id')
getCommodityById(@Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))id: number
) {console.log(id)return this.commodityService.findById(id)
}

绑定其他转换管道(所有 Parse*管道)的工作原理类似。这些管道都在验证路由参数、查询字符串参数和请求正文值的上下文中工作。

定制管道

虽然 NestJS 提供了内置的管道,但是我们也可以根据自己的需求来定制管道。我们来创建一个简单的验证管道。具体代码如下:

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 当前处理的方法参数的元数据

元数据对象具有以下属性:

export interface ArgumentMetadata {type: "body" | "query" | "param" | "custom";metatype?: Type<unknown>;data?: string;
}

属性说明:

属性说明
type指示参数是否是 body @Body()、 query @Query()、 param@Param()或自定义参数
metatype提供参数的元类型,例如 String。注意:该值是 undefined 您在路由处理程序方法签名中省略类型声明或使用普通 JavaScript 时的值。
data传递给装饰器的字符串,例如@Body(‘string’)。如果 undefined 您将装饰器括号留空。

基于模式的验证

业务基础代码:

@Post('/save')
saveItem(@Body() accountItem: CreateAccountDto) {return this.accountService.save(accountItem)
}export class CreateAccountDto {name: string;age: number;breed: string;
}

看到上述的代码后,我们会考虑希望确保对 saveItem 方法的请求参数都是有效的数据类型。所以我们必须验证对象的三个成员 CreateAccountDto。我们可以在路由处理程序方法内执行此操作,但这样做并不理想,因为它会破坏单一责任规则(SRP)。

另一种方法可能是创建一个验证器类并在那里委派任务。这样做的缺点是我们必须记住在每个方法的开头调用这个验证器。

创建验证中间件怎么样?这可能有效,但不幸的是,不可能创建可在整个应用程序的所有上下文中使用的通用中间件。这是因为中间件不知道执行上下文,包括将被调用的处理程序及其任何参数。

接下来我们借助joi库来简化数据校验的工作,首先我们先安装所需要的包:

npm install --save joi

编写验证参数的管道,具体代码如下:

import {PipeTransform,Injectable,ArgumentMetadata,BadRequestException,
} from "@nestjs/common";
import { ObjectSchema } from "joi";@Injectable()
export class JoiValidationPipe implements PipeTransform {constructor(private schema: ObjectSchema) {}transform(value: any, metadata: ArgumentMetadata) {const { error } = this.schema.validate(value);if (error) {throw new BadRequestException("请求参数类型不正确");}return value;}
}

绑定管道以及声明 Joi 的校验规则,具体代码如下:

import { Body, Controller, Get, Param, Post, UsePipes } from "@nestjs/common";
import * as Joi from "joi"; // 这里的导入方式写成import Joi from "joi" 会报错需要注意
import { CreateAccountDto } from "src/dto/createAccount.dto";
import { JoiValidationPipe } from "src/pipe/JoiValidation.pipe";
import { AccountService } from "src/service/account.service";
import { CommodityService } from "src/service/commodity.service";// Joi 的校验规则可以放在控制器中也可以放在单独的文件夹下
const createCatSchema = Joi.object({name: Joi.string().required(),age: Joi.number().required(),breed: Joi.string().required(),
});@Controller("account/v1")
export class AccountController {constructor(private commodityService: CommodityService,private accountService: AccountService) {}@Get("/info")getInfo(@Param() params: { id: number }) {const _commodity = this.commodityService.findById(params.id);return this.accountService.findById(_commodity?.id);}@Post("/save")@UsePipes(new JoiValidationPipe(createCatSchema))saveItem(@Body() accountItem: CreateAccountDto) {console.log(accountItem);return this.accountService.save(accountItem);}
}

当发起如下的请求参数时,接口会返回具体的错误信息。

// 请求参数
{"name": "123","age": "12asd","breed": "123"
}// 返回结果
{"statusCode": 400,"message": "请求参数类型不正确","error": "Bad Request"
}

类验证器

除了可以使用基于模式的验证器外,我们还可以使用类验证器来实现验证管道。要实现类验证器的管道只需几步就可以实现:

  • 首先安装相关插件
npm i --save class-validator class-transformer
  • 编写相应的dto文件
import { IsInt, IsString } from "class-validator";export class CreateAccountDto {@IsString()name: string;@IsInt()age: number;@IsString()breed: string;
}
  • 编写类验证器管道
import {PipeTransform,Injectable,ArgumentMetadata,BadRequestException,
} from "@nestjs/common";
import { validate } from "class-validator";
import { plainToInstance } from "class-transformer";@Injectable()
export class ValidationPipe implements PipeTransform<any> {async transform(value: any, { metatype }: ArgumentMetadata) {if (!metatype || !this.toValidate(metatype)) {return value;}const object = plainToInstance(metatype, value);const errors = await validate(object);if (errors.length > 0) {throw new BadRequestException("Validation failed");}return value;}private toValidate(metatype: Function): boolean {const types: Function[] = [String, Boolean, Number, Array, Object];return !types.includes(metatype);}
}
  • 绑定类验证器管道
@Post('/save')
saveItem(@Body(new ValidationPipe()) accountItem: CreateAccountDto) {console.log(accountItem)return this.accountService.save(accountItem)
}

上述的类验证器管道代码需要做出以下几点说明:

  • transform()方法被标记为async。因为 NestJS 支持同步和异步管道。我们使用此方法 async 是因为某些类验证器验证可以是异步的(利用 Promises)

  • toValidate()方法是一个辅助方法,它的方法处理机制是,当传入的参数是 JavaScript 默认数据类型时,它就不会验证数据是否合规(这些参数不能附加验证装饰器,因此没有理由通过验证步骤运行它们,类验证器是首先验证数据类型)

  • plainToInstance()方法是将普通 JavaScript 参数对象转换为类型化对象,以便我们可以应用验证。我们必须这样做的原因是,当从网络请求反序列化时,传入的 post body 对象没有任何类型信息(这是底层平台(例如 Express)的工作方式)。类验证器需要使用我们之前为 DTO 定义的验证装饰器,因此我们需要执行此转换以将传入的主体视为适当装饰的对象,而不仅仅是普通对象

全局范围管道

全局范围的管道可以使用useGlobalPipes来装载,具体实例如下:

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

全局范围管道会应用于整个应用程序、每个控制器和每个路由。当我们需要在某个模块注入全局范围管道的依赖项是不行的,因为全局范围管道是在没有上下文环境下完成,所以没有上下文的支持,模块是不能找到对应实例。为了解决这个问题,我们可以用下面的方式来进行管道的注入:

import { Module } from "@nestjs/common";
import { APP_PIPE } from "@nestjs/core";@Module({providers: [{provide: APP_PIPE,useClass: ValidationPipe,},],
})
export class AppModule {}

内置的校验管道

提醒一下,您不必自己构建通用验证管道,因为它 ValidationPipe 是由 Nest 开箱即用提供的。内置 ValidationPipe 提供了很多的功能,在后续的笔记中会做展示。

转换用例

验证并不是自定义管道的唯一用例。在笔记开头我们说过管道的另一个作用就是将输入数据转换为所需的格式。这是可能的,因为从函数返回的值 transform 完全覆盖了参数的先前值。

这什么时候有用?请考虑,有时从客户端传递的数据需要进行一些更改(例如将字符串转换为整数),然后才能由路由处理程序方法正确处理。此外,一些必需的数据字段可能会丢失,我们希望应用默认值。转换管道可以通过在客户端请求和请求处理程序之间插入处理函数来执行这些功能。

这是一个简单的函数 ParseIntPipe,负责将字符串解析为整数值。(如上所述,Nest 有一个 ParseIntPipe 更复杂的内置功能;我们将其作为自定义转换管道的简单示例)。具体实例如下:

import {PipeTransform,Injectable,ArgumentMetadata,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;}
}

然后我们可以将此管道绑定到所选参数,如下所示:

@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {return this.catsService.findOne(id);
}

提供默认值

Parse*管道期望定义参数的值。null 他们在接收或值时抛出异常 undefined。为了允许端点处理缺失的查询字符串参数值,我们必须提供一个要在管道对 Parse\*这些值进行操作之前注入的默认值。就是 DefaultValuePipe 为了这个目的。DefaultValuePipe 只需在@Query()装饰器中相关管道之前实例化 a 即可 Parse\*,如下所示:

@Get()
async findAll(@Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean,@Query('page', new DefaultValuePipe(0), ParseIntPipe) page: number,
) {return this.catsService.findAll({ activeOnly, page });
}

http://www.mrgr.cn/p/61434066

相关文章

Python 进阶(三):正则表达式(re 模块)

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 1. 导入re模块2. re模块中的常用函数2.1 re.search()2.2 re.findall()2.3 re.sub()2.4…

开源快速开发平台:做好数据管理,实现流程化办公!

做好数据管理&#xff0c;可以提升企业的办公协作效率&#xff0c;实现数字化转型。开源快速开发平台是深受企业喜爱的低代码开发平台&#xff0c;拥有多项典型功能&#xff0c;是可以打造自主可控快速开发平台&#xff0c;实现一对一框架定制的软件平台。在快节奏的社会中&…

游戏APP开发:创新设计的秘诀

在游戏 APP开发中&#xff0c;创新设计是游戏开发公司的一大追求&#xff0c;为了可以为用户带来更好的游戏体验&#xff0c;这就需要对游戏 APP开发进行创新设计。那么&#xff0c;游戏 APP开发中的创新设计是什么呢&#xff1f;接下来&#xff0c;我们就一起来看看吧。 想要…

基于深度学习的高精度80类动物目标检测系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度80类动物目标检测识别系统可用于日常生活中或野外来检测与定位80类动物目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的80类动物目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YO…

Spring MVC异常处理【单个控制异常处理器、全局异常处理器、自定义异常处理器】

目录 一、单个控制器异常处理 1.1 控制器方法 1.2 编写出错页面 1.3 测试结果 二、全局异常处理 2.1 一个有异常的控制器类 2.2 全局异常处理器类 2.3 测试结果 三、自定义异常处理器 3.1 自定义异常处理器 3.2 测试结果 往期专栏&文章相关导读 1. Maven系列…

Linux - make/Makefifile

0.背景 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的规则来指定&#xff0c;哪些文件需要先编译&#xff0c;哪些文件需…

理解构建LLM驱动的聊天机器人时的向量数据库检索的局限性 - (第1/3部分)

本博客是一系列文章中的第一篇&#xff0c;解释了为什么使用大型语言模型&#xff08;LLM&#xff09;部署专用领域聊天机器人的主流管道成本太高且效率低下。在第一篇文章中&#xff0c;我们将讨论为什么矢量数据库尽管最近流行起来&#xff0c;但在实际生产管道中部署时从根本…

华为eNSP:路由引入

一、拓扑图 二、路由器的配置 1、配置路由器的IP AR1&#xff1a; [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 1.1.1.1 24 [Huawei-GigabitEthernet0/0/0]qu AR2&#xff1a; [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 1.1.1.2 24 [Huaw…

微分流形2:流形上的矢量场和张量场

来了来了&#xff0c;切向量&#xff0c;切空间。流形上的所有的线性泛函的集合&#xff0c;注意是函数的集合。然后取流形上的某点p&#xff0c;它的切向量为&#xff0c;线性泛函到实数的映射。没错&#xff0c;是函数到实数的映射&#xff0c;是不是想到了求导。我们要逐渐熟…

Git下载与安装

文章目录 一、Git下载二、Git安装1.双击下载好的安装包进行安装2.Next3.选择Git的安装目录(不要带有中文和空格)→Next4.Next5.Next6.Next7.Next8.Next9.Next10.Next11.Next12.Next13.Next14.Next15.Next16.Install17.等待安装18.Finish19.鼠标光标放到系统桌面右击看到如下图所…

数学分析:流形的线性代数回顾

因为是线性的&#xff0c;所以可以把所有的系数都提取出去。这也是多重线性代数的性质。可以看成基本的各项自变量的乘法。 这里可以看到两个不同基向量下&#xff0c;他们的坐标转化关系。 引出了张量积&#xff0c;也就是前面提到的内容。 对偶空间的例子总是比较美好。 因为…

VMware Linux 可视化增加磁盘

1、VMware 增加磁盘 2、disks挂载磁盘 此处我挂载的是20G磁盘&#xff0c;截图只是用5G的做过程演示例子。 3、验证挂载磁盘

CSS 瀑布流效果效果

示例 <!DOCTYPE html> <html lang="cn"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>瀑布流效果</title><style>…

【数据结构】实验三:链表

实验三链表 一、实验目的与要求 1&#xff09;熟悉链表的类型定义&#xff1b; 2&#xff09;熟悉链表的基本操作&#xff1b; 3&#xff09;灵活应用链表解决具体应用问题。 二、实验内容 1&#xff09;请设计一个单链表的存储结构&#xff0c;并实现单链表中基本运算算…

神经数据库:用于使用 ChatGPT 构建专用 AI 代理的下一代上下文检索系统 — (第 2/3 部分)

书接上回理解构建LLM驱动的聊天机器人时的向量数据库检索的局限性 - &#xff08;第1/3部分&#xff09;_阿尔法旺旺的博客-CSDN博客 其中我们强调了&#xff08;1&#xff09;嵌入生成&#xff0c;然后&#xff08;2&#xff09;使用近似近邻&#xff08;ANN&#xff09;搜索…

ZooKeeper原理剖析

1.ZooKeeper简介 ZooKeeper是一个分布式、高可用性的协调服务。在大数据产品中主要提供两个功能&#xff1a; 帮助系统避免单点故障&#xff0c;建立可靠的应用程序。提供分布式协作服务和维护配置信息。 2.ZooKeeper结构 ZooKeeper集群中的节点分为三种角色&#xff1a;Le…

Linux 多线程并发Socket服务端的实现( 11 ) -【Linux通信架构系列 】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…

1221. 四平方和(超详细!!)

输入样例&#xff1a; 5输出样例&#xff1a; 0 0 1 2 本题思路&#xff1a;以空间换时间 由于暴力解法我们至少要枚举三个数&#xff0c;然后计算出第四个数 呢么需要进行三重循环,时间复杂度大概为O(n3),则会超时 所以我们要进行优化来降低时间复杂度 我们的思路是&#xf…

【Rust笔记】意译解构 Object Safety for trait

意译解构Object Safety for trait 借助【虚表vtable】对被调用成员函数【运行时内存寻址】的作法允许系统编程语言Rust模仿出OOP高级计算机语言才具备的【专用多态Ad-hoc Polymorphism】特性。 计算机高级语言中的“多态”术语是一个泛指。它通常可被细化为 基于继承关系的“子…

热风梳C22.2 NO.3亚马逊加拿大审核标准

加拿大是目前亚马逊所有站点中&#xff0c;商业规模大、发展势头迅猛的站点之一。亚马逊加拿大站每月吸引近1600万访客。其优势在于在加拿大&#xff0c;目前平台的竞争较小&#xff0c;商家容易出单。既然加拿大站有这么多优势&#xff0c;那产品上架需要有哪些检测认证合规方…