NestJs學習之旅(8)——管道

歡迎持續關注NestJs學習之旅系列文章typescript

img

管道

熟悉Linux命令的夥伴應該對「管道運算符」不陌生。npm

ls -la | grep demo

"|" 就是管道運算符,它把左邊命令的輸出做爲輸入傳遞給右邊的命令,支持級聯,如此一來,即可以經過管道運算符進行復雜命令的交替運算。json

img

NestJs中的管道有着相似的功能,也能夠級聯處理數據。NestJs管道經過@Injectable()裝飾器裝飾,須要實現PipeTransform接口。bootstrap

NestJs中管道的主要職責以下:bash

  • 數據轉換 將輸入數據轉換爲所需的輸出
  • 數據驗證 接收客戶端提交的參數,若是經過驗證則繼續傳遞,若是驗證未經過則提示錯誤

執行順序

在前面的文章中咱們討論了中間件控制器路由守衛,結合本問討論的管道,可能有些讀者會對這些組件的執行順序提出疑問:這些東西執行的順序究竟是怎樣的?app

執行順序也不用找資料,本身在這些組件執行時加上日誌便可,我得出的結論以下:框架

客戶端請求 -> 中間件 -> 路由守衛 -> 管道 -> 控制器方法

開發管道

數據轉換類的管道就不詳細解釋了:async

給你一個value和元數據,你的return值就是轉換後的值。

NestJs內置了ValidationPipe、ParseIntPipe和ParseUUIDPipe。爲了更好地理解它們的工做原理,咱們以ValidationPipe(驗證器管道)爲例來演示管道的使用。學習

PipeTransform

這是管道必須實現的接口,該接口定義以下:this

export interface PipeTransform<T = any, R = any> {
    transform(value: T, metadata: ArgumentMetadata): R;
}
  • value <T> 輸入參數,T爲輸入參數類型
  • metadata <ArgumentMetadata> value的元數據,包括參數來源,參數類型等等
  • <R> 輸出參數,R爲輸出參數類型

ArgumentMetadata

用來描述當前處理value的元數據接口,接口定義以下:

export interface ArgumentMetadata {
  readonly type: 'body' | 'query' | 'param' | 'custom';
  readonly metatype?: Type<any>;
  readonly data?: string;
}

這個接口你們可能看不明白,不要緊,等下會有具體示例來進行解讀。

  • type <string> 輸入數據的來源
  • metatype <Type<any>> 注入數據的類型
  • data <string|undefined>傳遞給裝飾器的數據類型

例如以下控制器方法:

@Post()
login(@Query('type') type: number) { // type 爲登陸類型參數,相似手機號登陸爲1,帳號登陸爲2的例子
  
}

上述例子的元數據以下:

  • type query @Query裝飾器是讀取GET參數
  • metatype Number type的類型符號
  • data type 傳遞給@Query裝飾器的參數爲「type」

驗證器示例

下面以用戶登陸時校驗帳號密碼來講明驗證器管道的使用,規則以下:

  • 帳號必須是字符串,長度6-20
  • 密碼不能爲空

DTO定義

DTO在Java中是Data Transfer Object,簡單來講就是對數據的一層包裝。我們NestJs中用這個東西通常是爲了防止非法字段的提交和IDE自動提示(偷笑)。

使用規則裝飾器須要安裝class-validator和class-transformer:

npm i --save class-validator class-transformer

登陸表單定義以下:

// userLogin.dto.ts
export class UserLoginDto {
  @IsString()
  @Length(6, 20, { message: '長度不合法' })
  readonly username: string;
  @Length(1)
  readonly password: string;
}

管道定義

因爲我們的管道是通用的,也就是驗證什麼內容是由外部決定的,管道只負責「你給我數據和規則,我來校驗」。因此我們須要使用到裝飾器元數據。

// validate.pipe.ts
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';

@Injectable()
export class ValidatePipe implements PipeTransform {

  async transform(value: any, { metatype }: ArgumentMetadata): Promise<any> {
    if (!metatype || !this.toValidate(metatype)) { // 若是不是注入的數據且不須要驗證,直接跳過處理
      return value;
    }
    // 數據格式轉換
    const object = plainToClass(metatype, value);
    // 調用驗證
    const errors = await validate(object);
    // 若是錯誤長度大於0,證實出錯,須要拋出400錯誤
    if (errors.length > 0) {
      throw new BadRequestException(errors);
    }
    return value;
  }

  /**
   * 須要驗證的數據類型
   * @param metatype
   */
  private toValidate(metatype: any): boolean {
    const types = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

控制器定義

今天的主角是管道,因此控制器層就不寫邏輯了

// user.controller.ts
@Post('login')
@UsePipes(ValidatePipe)
login(@Body() userLoginDto: UserLoginDTO) {
  return {errcode:0, errmsg: 'ok'};
}

運行項目

項目根目錄執行如下命令便可運行NestJs項目:

npm run start

項目運行後可使用Postman來驗證一下:

請求數據1

{
    
}

響應數據1

{
    "statusCode": 400,
    "error": "Bad Request",
    "message": [
        {
            "target": {},
            "property": "username",
            "children": [],
            "constraints": {
                "length": "長度不合法",
                "isString": "username must be a string"
            }
        },
        {
            "target": {},
            "property": "password",
            "children": [],
            "constraints": {
                "length": "password must be longer than or equal to 1 characters"
            }
        }
    ]
}

請求數據2

{
    "username":"xialeistudio"
}

響應數據2

{
    "statusCode": 400,
    "error": "Bad Request",
    "message": [
        {
            "target": {
                "username": "xialeistudio"
            },
            "property": "password",
            "children": [],
            "constraints": {
                "length": "password must be longer than or equal to 1 characters"
            }
        }
    ]
}

請求數據3

{
    "username":"xialeistudio",
    "password":"111111"
}

響應數據3

[]

注意事項

上文演示了ValidatePipe的實現,生產環境直接使用NestJs提供的ValidationPipe便可。咱們能夠在main.ts中使用全局管道。

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

結尾

和筆者使用的SpringBoot中驗證框架對比一下以後發現,NestJs驗證管道所實現的功能還真不比SpringBoot差,看來官方說的「下一代Node.js全棧開發框架」確實不是蓋的!

若是您以爲有所收穫,分享給更多須要的朋友,謝謝!

若是您想交流關於NestJs更多的知識,歡迎加羣討論!

20190827145318

相關文章
相關標籤/搜索