??Hi~ 大家好,我是小鑫同學,資深 IT 從業(yè)者,InfoQ 的簽約作者,擅長前端開發(fā)并在這一領(lǐng)域有多年的經(jīng)驗,致力于分享我在技術(shù)方面的見解和心得
??技術(shù)&代碼分享
- 我在 94Code 總結(jié)技術(shù)學習;
- 我在 1024Code 在線編寫代碼;
- 我在 Github 參與開源學習;
??推薦幾個好用的工具
- var-conv 適用于VSCode IDE的代碼變量名稱快速轉(zhuǎn)換工具
- generator-vite-plugin 快速生成Vite插件模板項目
- generator-babel-plugin 快速生成Babel插件模板項目
進入正題
在 Nestjs 中管道是具有 @Injectable()
裝飾器且已實現(xiàn) PipeTransform
接口的類。
管道(Pipe)的作用
管道(Pipe)作用在每個控制器的處理方法上,也就是當每一個請求被路由到具體的控制器的方法后會先通過管道(Pipe)對傳入的請求參數(shù)進行 轉(zhuǎn)換 和 驗證,保證數(shù)據(jù)在被正式處理前是完全合法的。
管道(Pipe)的使用
Nestjs 中內(nèi)置了下列的9個管道,利用這些管道可以輕松的驗證路由參數(shù)、查詢參數(shù)和請求正文是否合法,下面通過兩個例子一起看一下管道的使用。
ParseIntPipe | ParseFloatPipe |
ParseBoolPipe | ParseArrayPipe |
ParseUUIDPipe | ParseEnumPipe |
ParseFilePipe | |
DefaultValuePipe | ValidationPipe |
findUserById
是用來根據(jù)用戶 ID 獲取用戶信息的處理函數(shù),期望id
由客戶端傳來的必須是數(shù)字類型。
@Controller('users')
export class UsersController {
@Get(':id')
findUserById(@Param('id') id: number): string {
return `The ID of this user is ${id}`;
}
}
現(xiàn)在由于缺少對路由參數(shù)類型的校驗,此時客戶端在傳遞非數(shù)字類型的ID時并不會收到合理的提醒,這樣很容易造成服務(wù)端業(yè)務(wù)邏輯的異常,有入庫的操作的話還會造成垃圾數(shù)據(jù)。所以可將 ParseIntPipe
管道類直接添加到 @Param()
裝飾器的第二位參數(shù),如下圖:
@Controller('users')
export class UsersController {
@Get(':id')
findUserById(@Param('id', ParseIntPipe) id: number): string {
return `The ID of this user is ${id}`;
}
}
增加 ParseIntPipe
管道的限制后,當客戶端再次傳遞非數(shù)字類型的ID時就會收到對應(yīng)的提示。
上面的例子中使用了管道類而非管道的實例是因為 Nestjs 基于 IoC 的設(shè)計在框架內(nèi)部可以自動對類進行實例化操作,管道同時也支持通過構(gòu)造函數(shù)傳遞選項的方式自定義內(nèi)置管道的行為。
下面這個 findUserByUUID
函數(shù)中使用的 ParseUUIDPipe
管道默認情況下是支持接收不同版本的 UUID 的,但在例子中我們限制只可以接收 v5 版本的 UUID,就需要實例化 ParseUUIDPipe
并在構(gòu)造函數(shù)中指定具體的 version
。
@Get(':uuid')
findUserByUUID(
@Param('uuid', new ParseUUIDPipe({ version: '5' })) uuid: string,
): string {
return `The UUID of this user is ${uuid}`;
}
基于 schema 的驗證
在 createUser
處理函數(shù)中要求客戶端傳遞一份包含 name
、age
和 gender
的數(shù)據(jù),對于這種復(fù)雜的數(shù)據(jù)結(jié)構(gòu)來說可以引入 schema
(前端表單校驗常用技術(shù))來配合自定義管道實現(xiàn)。
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 中的三個屬性均設(shè)置為必填項。
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
創(chuàng)建一個公共的管道,在 transform
函數(shù)中使用已經(jīng)注入的ObjectSchema
對象提供的 validate
函數(shù)對請求參數(shù) 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
處理函數(shù)級別了,需要用到 @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 的驗證就可以利用為已創(chuàng)建的 CreateUserDto
增加驗證相關(guān)的裝飾器并配合通過的管道即可完成,從而可以少維護一份文件,避免不一致造成的問題。
首先執(zhí)行 npm i --save class-validator class-transformer
安裝必要的模塊,接著為 CreateUserDto
增加驗證相關(guān)的裝飾器。
import { IsString, IsNumber, IsBoolean, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string;
@IsNumber()
@IsNotEmpty()
age: number;
@IsBoolean()
@IsNotEmpty()
gender: boolean;
}
接著執(zhí)行 nest g pi dto-validation
創(chuàng)建一個公共的管道,在這個管道中需要做這么幾件事情:
- 解構(gòu) metadata 參數(shù),獲取請求體參數(shù)的元類型。
- 定義私有函數(shù)
toValidation
,跳過非DTO的類型(非Javascript原類型)。 - 使用
plainToInstance
將元類型和請求體參數(shù)轉(zhuǎn)為可驗證的類型對象。 - 通過
validate
函數(shù)執(zhí)行校驗,校驗未通過則拋出合理的異常信息。
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 所指的參數(shù)的元類型僅為Javascript原生類型的話則跳過校驗,這里只關(guān)注了對定義的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 管道可以完全支持上述兩種驗證方式,我們不必為自定義驗證管道花費時間。
提供默認值
提供默認值可以看做是管道在轉(zhuǎn)換場景的一個體現(xiàn),增加默認值的處理可以使得服務(wù)端的代碼更加的健壯。這里使用到了內(nèi)置的 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
實例的注冊,另一個是基礎(chǔ)跟模塊的注冊。
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 {}
總結(jié)
以上就是 Nest 中管道類的使用方式,也是保證參數(shù)正常接收、正常入庫的必要手段。文章來源:http://www.zghlxwxcb.cn/news/detail-484247.html
如果看完覺得有收獲,歡迎點贊、評論、分享支持一下。你的支持和肯定,是我堅持寫作的動力~文章來源地址http://www.zghlxwxcb.cn/news/detail-484247.html
到了這里,關(guān)于NestJs 管道(Pipe)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!