NestJs 管道(Pipe)

news2024/12/23 22:13:10

🎄Hi~ 大家好,我是小鑫同学,资深 IT 从业者,InfoQ 的签约作者,擅长前端开发并在这一领域有多年的经验,致力于分享我在技术方面的见解和心得

🚀技术&代码分享

  • 我在 94Code 总结技术学习;
  • 我在 1024Code 在线编写代码;
  • 我在 Github 参与开源学习;

😇推荐几个好用的工具

  • var-conv 适用于VSCode IDE的代码变量名称快速转换工具
  • generator-vite-plugin 快速生成Vite插件模板项目
  • generator-babel-plugin 快速生成Babel插件模板项目

进入正题

Nestjs 中管道是具有 @Injectable() 装饰器且已实现 PipeTransform 接口的类。

管道(Pipe)的作用

管道(Pipe)作用在每个控制器的处理方法上,也就是当每一个请求被路由到具体的控制器的方法后会先通过管道(Pipe)对传入的请求参数进行 转换验证,保证数据在被正式处理前是完全合法的。

管道(Pipe)的使用

Nestjs 中内置了下列的9个管道,利用这些管道可以轻松的验证路由参数、查询参数和请求正文是否合法,下面通过两个例子一起看一下管道的使用。

ParseIntPipeParseFloatPipe
ParseBoolPipeParseArrayPipe
ParseUUIDPipeParseEnumPipe
ParseFilePipe
DefaultValuePipeValidationPipe

findUserById 是用来根据用户 ID 获取用户信息的处理函数,期望id由客户端传来的必须是数字类型。

@Controller('users')
export class UsersController {
  @Get(':id')
  findUserById(@Param('id') id: number): string {
    return `The ID of this user is ${id}`;
  }
}

现在由于缺少对路由参数类型的校验,此时客户端在传递非数字类型的ID时并不会收到合理的提醒,这样很容易造成服务端业务逻辑的异常,有入库的操作的话还会造成垃圾数据。所以可将 ParseIntPipe 管道类直接添加到 @Param() 装饰器的第二位参数,如下图:

@Controller('users')
export class UsersController {
  @Get(':id')
  findUserById(@Param('id', ParseIntPipe) id: number): string {
    return `The ID of this user is ${id}`;
  }
}

增加 ParseIntPipe 管道的限制后,当客户端再次传递非数字类型的ID时就会收到对应的提示。

上面的例子中使用了管道类而非管道的实例是因为 Nestjs 基于 IoC 的设计在框架内部可以自动对类进行实例化操作,管道同时也支持通过构造函数传递选项的方式自定义内置管道的行为。

下面这个 findUserByUUID 函数中使用的 ParseUUIDPipe 管道默认情况下是支持接收不同版本的 UUID 的,但在例子中我们限制只可以接收 v5 版本的 UUID,就需要实例化 ParseUUIDPipe 并在构造函数中指定具体的 version

@Get(':uuid')
findUserByUUID(
    @Param('uuid', new ParseUUIDPipe({ version: '5' })) uuid: string,
): string {
    return `The UUID of this user is ${uuid}`;
}

基于 schema 的验证

createUser 处理函数中要求客户端传递一份包含 nameagegender 的数据,对于这种复杂的数据结构来说可以引入 schema (前端表单校验常用技术)来配合自定义管道实现。

export class CreateUserDto {
  name: string;
  age: number;
  gender: boolean;
}

@Post()
createUser(@Body() createUserDto: CreateUserDto): string {
  return `${createUserDto.name} is the 100th user`;
}

首先需要引入 joi 模块和 @types/joi 模块,使用 ES 模块导入的方式导入 joi 时需要在 tsconfig.json 中启用 esModuleInterop 选项。接着使用 Joi 模块将 CreateUserDto 中的三个属性均设置为必填项。

import Joi from 'joi';

export const createUserSchema = Joi.object({
  name: Joi.string().required(),
  age: Joi.number().required(),
  gender: Joi.bool().required(),
});

定义完 schema 后可以使用 nest g pi joi-validation 创建一个公共的管道,在 transform 函数中使用已经注入的ObjectSchema 对象提供的 validate 函数对请求参数 value 做验证,当验证不通过是抛出合理的异常,反之通过。

@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('Validation failed');
    }
    return value;
  }
}

这里的管道就需要绑定到 createUser 处理函数级别了,需要用到 @UsePipes() 装饰器,并传入通过 Joi 定义的 schema

@Post()
@UsePipes(new JoiValidationPipe(createUserSchema))
createUser(@Body() createUserDto: CreateUserDto): string {
  return `${createUserDto.name} is the 100th user`;
}

当客户端未传递其中某一个字段时就会收到如下的提示信息。

基于 dto 的验证

在基于 schema 的验证中不仅编写了通用的 joi-validation 管道,还用 Joi 库编写了一份和 CreateUserDto 几乎一样的 schema 文件,每当 DTO 文件有变更时就需要同步维护 schema 文件。

基于 dto 的验证就可以利用为已创建的 CreateUserDto 增加验证相关的装饰器并配合通过的管道即可完成,从而可以少维护一份文件,避免不一致造成的问题。

首先执行 npm i --save class-validator class-transformer 安装必要的模块,接着为 CreateUserDto 增加验证相关的装饰器。

import { IsString, IsNumber, IsBoolean, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  name: string;

  @IsNumber()
  @IsNotEmpty()
  age: number;

  @IsBoolean()
  @IsNotEmpty()
  gender: boolean;
}

接着执行 nest g pi dto-validation 创建一个公共的管道,在这个管道中需要做这么几件事情:

  1. 解构 metadata 参数,获取请求体参数的元类型。
  2. 定义私有函数 toValidation,跳过非DTO的类型(非Javascript原类型)。
  3. 使用 plainToInstance 将元类型和请求体参数转为可验证的类型对象。
  4. 通过 validate 函数执行校验,校验未通过则抛出合理的异常信息。
import {
  ArgumentMetadata,
  BadRequestException,
  Injectable,
  PipeTransform,
} from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';

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

  /**
   * 当 metatype 所指的参数的元类型仅为Javascript原生类型的话则跳过校验,这里只关注了对定义的DTO的校验
   */
  private toValidation(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

再接着将 DtoValidationPipe 管道绑定到 createUser 处理方法并作验证。

@Post()
createUser(
  @Body(new DtoValidationPipe()) createUserDto: CreateUserDto,
): string {
  return `${createUserDto.name} is the 100th user`;
}

PS:Nestjs 提供的 ValidationPipe 管道可以完全支持上述两种验证方式,我们不必为自定义验证管道花费时间。

提供默认值

提供默认值可以看做是管道在转换场景的一个体现,增加默认值的处理可以使得服务端的代码更加的健壮。这里使用到了内置的 DefaultValuePipe 管道。

@Get()
findAllUsers(
  @Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe)
  activeOnly: boolean,
  @Query('page', new DefaultValuePipe(10), ParseIntPipe) page: number,
): string {
  return `This action return all users,request parameters:activeOnly: ${activeOnly},page:${page}`;
}

全局管道注册

除上述管道的注册位置,还支持全局注册,注册方式同全局异常过滤器的注册,一个是基于 app 实例的注册,另一个是基础跟模块的注册。

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 {}

总结

以上就是 Nest 中管道类的使用方式,也是保证参数正常接收、正常入库的必要手段。


如果看完觉得有收获,欢迎点赞、评论、分享支持一下。你的支持和肯定,是我坚持写作的动力~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/648868.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows下安装运行Kafka(最底下有遇到的坑与解决方法)

注:安装kafka需要提前安装Zookeeper 一、Zookeeper安装 1. 下载安装包 https://zookeeper.apache.org/releases.html 注意:要下载带bin的安装包 2. 解压并进入ZooKeeper目录,如:D:\onworking\apache-zookeeper-3.7.0-bin&…

ansible剧本模式特殊模块使用

Nginx安装剧本 ansible-playbook test1.yaml //补充参数 -k(-ask-pass):用来交互输入ssh密码 -K(-ask-become-pass):用来交互输入sudo密码 -u:指定用户 -e:命令行指定变量 --syntax-check…

【服务器数据恢复】HP LeftHand存储raid5不可用的数据恢复案例

HP LeftHand存储简介: HP LeftHand存储支持搭建RAID5、RAID6、RAID10磁盘阵列,支持卷快照,卷动态扩容等。服务端和客户端分别如下: LeftHand存储共有三个级别:物理磁盘、基于多个物理磁盘组成的逻辑磁盘(ra…

广东电信突发故障,手机没信号,对讲机的重要性再次凸显

当我们常用的通信网络出现故障时,就会面临全网瘫痪的情况,这个时候无线电通信就显得尤为重要了! 在6月8日下午两点左右,有多位广东电信的用户发现,自己的手机突然出现了打不出去电话,及上不了网的情况&…

Unity编辑器扩展-第四集-获取物体的方法

第三集链接:Unity编辑器扩展-第三集-添加按钮到组件菜单并且重置组件_菌菌巧乐兹的博客-CSDN博客 一、本节目标效果展示 1.改选中单个物体的名字 2.改选中所有物体的名字 3.选中了所有的物体,但只改第一层物体的名称 4.来个有用的(选中的所有…

Python进阶语法之列表推导式

Python进阶语法之列表推导式 Python列表推导式是Python中最有魅力的特性之一,它提供了一种优雅、简洁的方式来创建列表。这种语法不仅使得代码更加简洁,易读,而且在某些情况下还可以提高代码的执行效率。接下来,我们将一起深入探…

SpringBoot中@ControllerAdvice的三种使用场景

一、全局异常处理 代码示例如下: /*** author qinxun* date 2023-06-14* Descripion: 业务层异常枚举*/ public enum ServiceExceptionEnum {SUCCESS(0, "成功"),ERROR(1, "失败"),SYS_ERROR(1000, "服务端发生异常"),MISSING_REQUEST_PARAM_E…

使用同步信号量和互斥信号量解决生产者和消费者问题

生产者和消费者问题 生产者和消费者问题是一个经典的进程同步问题。在这个问题中,生产者不断地向缓冲区中写入数据,而消费者则从缓冲区中读取数据。生产者进程和消费者进程对缓冲区的操作是互斥的,即任意时刻只能有一个进程对这个缓冲区进行…

RTU电流采集上传

RTU电流采集上传 案例说明器件 物联网平台开发代码修改三元组 测试 案例说明 本案例使用HD1(RTU)检测外部电流,并将电流上传阿里云端。 压力传感器输出电流信号,读取压力传感器数值时需要检测电流大小。haasHD1(RTU)有两路ADC—…

深度学习应用篇-元学习[16]:基于模型的元学习-Learning to Learn优化策略、Meta-Learner LSTM

【深度学习入门到进阶】必看系列,含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍:【深度学习入门到进阶】必看系列,含激活函数、优化策略、损失函数、模型调优、归一化…

[C语言实现]数据结构堆之《害怕二叉树所以天赋全点到堆上了》

🥰作者: FlashRider 🌏专栏: 数据结构 🍖知识概要:详解堆的概念、小根堆与大根堆的区别、以及代码实现。 目录 什么是堆? 如何实现堆? 代码实现堆(小根堆) 定义堆以及堆的初始化和销毁。 堆的插入 堆…

LeetCode·每日一题·1177. 构建回文串检测·前缀和

作者:小迅 链接:https://leetcode.cn/problems/can-make-palindrome-from-substring/solutions/2309940/qian-zhui-he-zhu-shi-chao-ji-xiang-xi-by-n3ps/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获…

最新水文水动力模型在城市内涝、城市排水、海绵城市规划设计中深度应用

随着计算机的广泛应用和各类模型软件的发展,将排水系统模型作为城市洪灾评价与防治的技术手段已经成为防洪防灾的重要技术途径。本次培训将聚焦于综合利用GIS及CAD等工具高效地进行大规模城市排水系统水力模型的建立,利用SWMM实现排水系统水力模拟。讲解…

【RH850/U2A】:GreenHills编译配置

GreenHills编译配置 GreenHills语法.gpj文件.opt文件示例GreenHills编译器在编译我们的文件时涉及它需要哪些文件及相关配置呢?带着疑问我们开始来梳理。 我们还是以具体示例来展开(硬件平台:RH850 U2A8) GreenHills语法 一般我们是需要查看它的帮助文档的,文档在哪里呢?…

CSP第二轮/NOIP 比赛注意事项

一、在哪里写代码 主办方会提前在桌面已在 E 盘根目录下建立以考生准考证编号命名的文件夹,考生应检查该文件夹名称是否正确(包括编号及大小写字母),如有错误须立即上报监考人员,由监考人员进行更改。确认无误后,考生须为每道试题再单独建立一个子文件夹,子文件夹名与对应…

某互联网银行绿色金融背后的“安全秘诀”

​随着银保监会出台《银行业保险业绿色金融指引》、人民银行牵头制定《G20转型金融框架》的发布,金融行业正在持续加大对绿色金融支持力度。某互联网银行为了响应号召,采用数字化无纸化办公,线上零接触服务减少大量碳排放,成为国内…

oracle rac架构解读

一、oracle 数据库架构 单节点数据库,如果实例宕机了,如果一个业务链接在实例上面,那么这个业务就中断了。这个时候系统就不具有可用性了,那么这个时候单节点的可用性是很差的。 对于RAC来说,和单实例一样,…

新榜 | 小红书美妆用户趋势洞察报告

目前,小红书上聚集了大量年轻、高知的女性美妆用户,她们倾向于在小红书平台分享美妆护肤产品和使用经验,用户间的互动、分享氛围浓厚;而这些评论互动也传递了用户的真实诉求,理解用户的关注点对于企业和品牌来说将具有…

Vue中如何进行数据导入与Excel导入

Vue中如何进行数据导入与Excel导入 Vue是一款非常流行的JavaScript框架,它提供了一套用于构建用户界面的工具和库。在Vue中,我们可以使用多种方式来导入数据,包括从服务器获取数据、从本地存储获取数据、从文件中读取数据等等。其中&#xf…

如何提高职场沟通能力

如何提高职场沟通能力 在现代职场中,良好的沟通能力不仅有助于我们更好地完成工作任务,还能提高团队协作效率,降低矛盾和误解。本文将为你提供一些建议和技巧,帮助你提高职场沟通能力。 1. 倾听 倾听是沟通中最重要的技能之一…