管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口。管道有两个类型:

  • 转换:管道将输入数据转换为所需的数据输出。
  • 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常。管道在异常区域内运行。这意味着当抛出异常时,它们由核心异常处理程序和应用于当前上下文的 异常过滤器 处理。当在 Pipe 中发生异常,controller 不会继续执行任何方法。

在这两种情况下管道 参数(arguments) 会由 控制器(controllers)的路由处理程序进行处理,Nest 会在调用这个方法之前插入一个管道,管道会先拦截方法的调用参数,进行转换或是验证处理,然后用转换好或是验证好的参数调用原方法。

Nest内置了六个开箱即用的管道:

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

    1.自定义管道

    ```typescript // src/common/pipes/validate.pipe.ts import { Injectable, PipeTransform, ArgumentMetadata } from ‘@nestjs/common’;

/**

  • 管道应该具有@Injectable()装饰的类,且应实现PipeTransform接口,重写transform()
  • PipeTransform 是一个通用接口,其中 T 表示 value 的类型,R 表示 transform() 方法的返回类型。
  • transform()拥有value和metadata两个参数,value 是当前处理的参数,metadata是其元数据,元数据对象包含一些属性:
  • export interface ArgumentMetadata {
  • type: ‘body’ | ‘query’ | ‘param’ | ‘custom’;
  • metatype?: Type;
  • data?: string;
  • }
  • type:告诉我们该属性是一个 body @Body(),query @Query(),param @Param() 还是自定义参数。
  • metatype:属性的元类型,例如 String。 如果在函数签名中省略类型声明,或者使用原生 JavaScript,则为 undefined。
  • data:传递给装饰器的字符串,例如 @Body(‘string’)。 如果您将括号留空,则为 undefined。 / @Injectable() export class ValidationPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { //假设访问http://localhost:3000/hello/1 console.log(‘参数是:’, value); //参数是: { id: ‘1’ } /
    • 参数的元数据是: { metatype: [Function: Object], type: ‘param’, data: undefined } */ console.log(‘参数的元数据是:’, metadata); //抛出的异常能被异常过滤器捕获到 throw new Error(‘Method not implemented.’); } }
  1. <a name="ktjwN"></a>
  2. #### 1.1 在其他装饰器中使用管道
  3. ```typescript
  4. import { Controller, Get, Param } from '@nestjs/common';
  5. import { AppService } from './app.service';
  6. import { ValidationPipe } from './common/pipes/validate.pipe';
  7. @Controller()
  8. export class AppController {
  9. constructor(private readonly appService: AppService) {}
  10. @Get('/hello/:id')
  11. //在@Param()装饰器中使用管道
  12. getHello(@Param(new ValidationPipe()) id): string {
  13. return this.appService.getHello();
  14. }
  15. }

1.2 使用@UsePipe()装饰器使用管道

  1. import { Controller, Get, Param, UsePipes } from '@nestjs/common';
  2. import { AppService } from './app.service';
  3. import { ValidationPipe } from './common/pipes/validate.pipe';
  4. /*
  5. * @UsePipes既可以用在控制器类上(作用范围是控制器),也可以用于路由方法上(作用范围是路由方法)
  6. */
  7. @UsePipes(new ValidationPipe())
  8. @Controller()
  9. export class AppController {
  10. constructor(private readonly appService: AppService) {}
  11. @Get('/hello/:id')
  12. getHello(@Param() id): string {
  13. return this.appService.getHello();
  14. }
  15. }

1.3 全局使用管道

  1. import { NestFactory } from '@nestjs/core';
  2. import { AppModule } from './app.module';
  3. import { ValidationPipe } from './common/pipes/validate.pipe';
  4. async function bootstrap() {
  5. const app = await NestFactory.create(AppModule);
  6. //使用管道
  7. app.useGlobalPipes(new ValidationPipe());
  8. await app.listen(3000);
  9. }
  10. bootstrap();

1.5 Nest内置管道的使用案例

由于ValidationPipe和ParseArrayPipe管道需要同时安装 class-validator 和 class-transformer 包,暂不介绍。

  1. import {
  2. Controller,
  3. Get,
  4. Param,
  5. Query,
  6. ParseIntPipe,
  7. ParseBoolPipe,
  8. ParseUUIDPipe,
  9. DefaultValuePipe,
  10. UsePipes,
  11. } from '@nestjs/common';
  12. import { AppService } from './app.service';
  13. @Controller()
  14. export class AppController {
  15. constructor(private readonly appService: AppService) {}
  16. @Get('/hello/:id')
  17. getHello(@Param('id', new ParseIntPipe()) id): string {
  18. console.log(typeof id);
  19. return this.appService.getHello();
  20. }
  21. @Get('/findUser/:bool')
  22. findUser(@Param('bool', new ParseBoolPipe()) bool) {
  23. /**
  24. * ParseIntPipe管道用于将参数转为布尔类型
  25. * 访问localhost:3000/findUser/true
  26. * 未使用ParseIntPipe前 typeof id的值为string
  27. * 使用ParseIntPipe后 typeof id的值为boolean
  28. */
  29. return typeof bool;
  30. }
  31. @Get('/getUser')
  32. getUser(@Query('name', new DefaultValuePipe('zxp')) name) {
  33. /**
  34. * DefaultValuePipe管道用于设置参数默认值。
  35. * 当传入了name 查询查看那么就会使用传入的name参数,
  36. * 若未传递name查询参数则name默认为zxp。
  37. */
  38. return name;
  39. }
  40. }

2.使用工具库验证对象结构

Nest并为提供验证对象结构的管道,如需要使用验证对象结构则需要借助class-validator工具库,安装:

  1. #npm安装方式
  2. npm i --save class-validator class-transformer
  3. #yarn安装方式
  4. yarn add class-validator class-transformer

让我们定义一个userDto类(Dto即视图层传入的领域对象),并使用class-validator提供的一系列验证装饰器来装饰类的字段,从而提供应用程序的容错性,当出现传入参数验证错误能进行优雅的处理和友好的提示。

  1. import {
  2. IsString,
  3. IsDefined,
  4. IsInt,
  5. IsDateString,
  6. IsNotEmpty,
  7. Length,
  8. IsIn,
  9. Max,
  10. Min,
  11. } from 'class-validator';
  12. /**
  13. * class-validator工具库提供了超级多验证装饰器,这些注解最后一个参数都接收一个ValidationOptions对象,
  14. * ValidationOptions对象结构如下:
  15. * export interface ValidationOptions{
  16. * //如果验证值为数组,是否必须验证其每个项。
  17. * each?: boolean;
  18. * //验证失败的错误信息,可以是字符串也可以是返回字符串的函数
  19. * message?: string | ((validationArguments: ValidationArguments) => string);
  20. * //用于当前验证的验证组
  21. * groups?: string[];
  22. * //是否执行所有验证,无论使用哪种验证组
  23. * always?: boolean;
  24. * //上下文
  25. * context?: any;
  26. * }
  27. */
  28. export class UserDto {
  29. @IsString({ message: 'userName不是string类型' })
  30. @IsNotEmpty({ message: 'userName不能为空' })
  31. @Length(0, 20, { message: 'userName的长度必须大于0小于20' })
  32. @IsDefined({ message: 'userName不能为空' })
  33. userName: string;
  34. @IsInt({ message: 'age不是整形数字' })
  35. @Min(0, { message: 'age的范围是0到100' })
  36. @Max(100, { message: 'age的范围是0到100' })
  37. @IsNotEmpty({ message: 'age不能为空' })
  38. age: number;
  39. /**
  40. * @IsDateString() 验证字符串日期格式,
  41. * 如果“strict”为“true”,则对有效日期执行其他检查,例如,使日期无效,如“2009-02-29”。
  42. */
  43. @IsDateString({ strict: true }, { message: '生日应为日期类型' })
  44. birthday: Date;
  45. /**
  46. * sex为0表示男,1表示女,sex只能枚举0和1
  47. */
  48. @IsInt()
  49. @IsDefined({ message: '性别不能为空' })
  50. @IsIn([0, 1], { message: '性别只能为0或1' })
  51. sex: number;
  52. }

然后定义一个验证管道,使用class-transformer提供的plainToClass()将参数转换为可验证的类型对象,使用class-validator提供的validate()对需要验证的对象进行验证,validate()返回一个ValidationError数组,该数组描述了验证错误信息。

  1. import {
  2. PipeTransform,
  3. ArgumentMetadata,
  4. BadRequestException,
  5. Injectable,
  6. } from '@nestjs/common';
  7. import { validate, ValidationError } from 'class-validator';
  8. import { plainToClass } from 'class-transformer';
  9. @Injectable()
  10. export class ValidationPipe implements PipeTransform<any> {
  11. async transform(value: any, { metatype }: ArgumentMetadata) {
  12. //对于无参数或参数类型为基本类型就跳过验证
  13. if (!metatype || !this.toValidate(metatype)) return value;
  14. //将JavaScript 的参数为可验证的类型对象
  15. const object = plainToClass(metatype, value);
  16. const errors = await validate(object);
  17. if (errors.length > 0) {
  18. const error: ValidationError = errors[0];
  19. const errorMessage: string = Object.values(error.constraints)[0];
  20. throw new BadRequestException(errorMessage);
  21. }
  22. }
  23. private toValidate(metatype: Function): boolean {
  24. const types: Function[] = [String, Boolean, Number, Array, Object];
  25. return !types.includes(metatype);
  26. }
  27. }

在控制器中使用自定义管道验证数据。

  1. import {
  2. Controller,
  3. Post,
  4. Body,
  5. } from '@nestjs/common';
  6. import { ValidationPipe } from './common/pipes/ValidationPipe.pipe';
  7. @Controller()
  8. export class AppController {
  9. @Post('/addUser')
  10. addUser(@Body(new ValidationPipe()) userdto: UserDto): string {
  11. return '验证成功';
  12. }
  13. }

先来一个传入正确参数的测试例子:
11111.jpg

再来几个验证失败的测试例子
222.jpg
3333.jpg444.jpg
5555.jpg
6666.jpg

因为ValidationPipe管道可以适用于所有类型的验证,所以我们应该把ValidationPipe注册为全局的,全局管道作用于应用程序、每个控制器和每个路由处理程序。这样其他模块的使用ValidationPipe带来的验证功能。

  1. //main.ts
  2. import { NestFactory } from '@nestjs/core';
  3. import { AppModule } from './app.module';
  4. import { ValidationPipe } from './common/pipes/ValidationPipe.pipe';
  5. async function bootstrap() {
  6. const app = await NestFactory.create(AppModule);
  7. app.useGlobalPipes(new ValidationPipe());
  8. await app.listen(3000);
  9. }
  10. bootstrap();

就依赖注入而言,从任何模块外部注册的全局管道(如上例所示)无法注入依赖,因为它们不属于任何模块。为了解决这个问题,可以使用以下构造直接为任何模块设置管道。

  1. import { Module } from '@nestjs/common';
  2. import { APP_PIPE } from '@nestjs/core';
  3. //app.module.ts
  4. @Module({
  5. providers: [
  6. {
  7. provide: APP_PIPE,
  8. useClass: ValidationPipe
  9. }
  10. ]
  11. })
  12. export class AppModule {}