管道是具有 @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.’); } }
<a name="ktjwN"></a>#### 1.1 在其他装饰器中使用管道```typescriptimport { Controller, Get, Param } from '@nestjs/common';import { AppService } from './app.service';import { ValidationPipe } from './common/pipes/validate.pipe';@Controller()export class AppController {constructor(private readonly appService: AppService) {}@Get('/hello/:id')//在@Param()装饰器中使用管道getHello(@Param(new ValidationPipe()) id): string {return this.appService.getHello();}}
1.2 使用@UsePipe()装饰器使用管道
import { Controller, Get, Param, UsePipes } from '@nestjs/common';import { AppService } from './app.service';import { ValidationPipe } from './common/pipes/validate.pipe';/** @UsePipes既可以用在控制器类上(作用范围是控制器),也可以用于路由方法上(作用范围是路由方法)*/@UsePipes(new ValidationPipe())@Controller()export class AppController {constructor(private readonly appService: AppService) {}@Get('/hello/:id')getHello(@Param() id): string {return this.appService.getHello();}}
1.3 全局使用管道
import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';import { ValidationPipe } from './common/pipes/validate.pipe';async function bootstrap() {const app = await NestFactory.create(AppModule);//使用管道app.useGlobalPipes(new ValidationPipe());await app.listen(3000);}bootstrap();
1.5 Nest内置管道的使用案例
由于ValidationPipe和ParseArrayPipe管道需要同时安装 class-validator 和 class-transformer 包,暂不介绍。
import {Controller,Get,Param,Query,ParseIntPipe,ParseBoolPipe,ParseUUIDPipe,DefaultValuePipe,UsePipes,} from '@nestjs/common';import { AppService } from './app.service';@Controller()export class AppController {constructor(private readonly appService: AppService) {}@Get('/hello/:id')getHello(@Param('id', new ParseIntPipe()) id): string {console.log(typeof id);return this.appService.getHello();}@Get('/findUser/:bool')findUser(@Param('bool', new ParseBoolPipe()) bool) {/*** ParseIntPipe管道用于将参数转为布尔类型* 访问localhost:3000/findUser/true* 未使用ParseIntPipe前 typeof id的值为string* 使用ParseIntPipe后 typeof id的值为boolean*/return typeof bool;}@Get('/getUser')getUser(@Query('name', new DefaultValuePipe('zxp')) name) {/*** DefaultValuePipe管道用于设置参数默认值。* 当传入了name 查询查看那么就会使用传入的name参数,* 若未传递name查询参数则name默认为zxp。*/return name;}}
2.使用工具库验证对象结构
Nest并为提供验证对象结构的管道,如需要使用验证对象结构则需要借助class-validator工具库,安装:
#npm安装方式npm i --save class-validator class-transformer#yarn安装方式yarn add class-validator class-transformer
让我们定义一个userDto类(Dto即视图层传入的领域对象),并使用class-validator提供的一系列验证装饰器来装饰类的字段,从而提供应用程序的容错性,当出现传入参数验证错误能进行优雅的处理和友好的提示。
import {IsString,IsDefined,IsInt,IsDateString,IsNotEmpty,Length,IsIn,Max,Min,} from 'class-validator';/*** class-validator工具库提供了超级多验证装饰器,这些注解最后一个参数都接收一个ValidationOptions对象,* ValidationOptions对象结构如下:* export interface ValidationOptions{* //如果验证值为数组,是否必须验证其每个项。* each?: boolean;* //验证失败的错误信息,可以是字符串也可以是返回字符串的函数* message?: string | ((validationArguments: ValidationArguments) => string);* //用于当前验证的验证组* groups?: string[];* //是否执行所有验证,无论使用哪种验证组* always?: boolean;* //上下文* context?: any;* }*/export class UserDto {@IsString({ message: 'userName不是string类型' })@IsNotEmpty({ message: 'userName不能为空' })@Length(0, 20, { message: 'userName的长度必须大于0小于20' })@IsDefined({ message: 'userName不能为空' })userName: string;@IsInt({ message: 'age不是整形数字' })@Min(0, { message: 'age的范围是0到100' })@Max(100, { message: 'age的范围是0到100' })@IsNotEmpty({ message: 'age不能为空' })age: number;/*** @IsDateString() 验证字符串日期格式,* 如果“strict”为“true”,则对有效日期执行其他检查,例如,使日期无效,如“2009-02-29”。*/@IsDateString({ strict: true }, { message: '生日应为日期类型' })birthday: Date;/*** sex为0表示男,1表示女,sex只能枚举0和1*/@IsInt()@IsDefined({ message: '性别不能为空' })@IsIn([0, 1], { message: '性别只能为0或1' })sex: number;}
然后定义一个验证管道,使用class-transformer提供的plainToClass()将参数转换为可验证的类型对象,使用class-validator提供的validate()对需要验证的对象进行验证,validate()返回一个ValidationError数组,该数组描述了验证错误信息。
import {PipeTransform,ArgumentMetadata,BadRequestException,Injectable,} from '@nestjs/common';import { validate, ValidationError } from 'class-validator';import { plainToClass } from 'class-transformer';@Injectable()export class ValidationPipe implements PipeTransform<any> {async transform(value: any, { metatype }: ArgumentMetadata) {//对于无参数或参数类型为基本类型就跳过验证if (!metatype || !this.toValidate(metatype)) return value;//将JavaScript 的参数为可验证的类型对象const object = plainToClass(metatype, value);const errors = await validate(object);if (errors.length > 0) {const error: ValidationError = errors[0];const errorMessage: string = Object.values(error.constraints)[0];throw new BadRequestException(errorMessage);}}private toValidate(metatype: Function): boolean {const types: Function[] = [String, Boolean, Number, Array, Object];return !types.includes(metatype);}}
在控制器中使用自定义管道验证数据。
import {Controller,Post,Body,} from '@nestjs/common';import { ValidationPipe } from './common/pipes/ValidationPipe.pipe';@Controller()export class AppController {@Post('/addUser')addUser(@Body(new ValidationPipe()) userdto: UserDto): string {return '验证成功';}}
先来一个传入正确参数的测试例子:
再来几个验证失败的测试例子




因为ValidationPipe管道可以适用于所有类型的验证,所以我们应该把ValidationPipe注册为全局的,全局管道作用于应用程序、每个控制器和每个路由处理程序。这样其他模块的使用ValidationPipe带来的验证功能。
//main.tsimport { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';import { ValidationPipe } from './common/pipes/ValidationPipe.pipe';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';//app.module.ts@Module({providers: [{provide: APP_PIPE,useClass: ValidationPipe}]})export class AppModule {}
