管道是具有 @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 在其他装饰器中使用管道
```typescript
import { 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.ts
import { 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 {}