上一篇介紹瞭如何使用中間件、攔截器、過濾器打造日誌系統,接下來將介紹後端永遠繞不過去的痛:參數驗證。前端
你是否曾經爲了驗證參數,寫了一大堆 if - else ?而後還要判斷各類參數類型?類似的結構在不一樣的方法裏判斷,卻又要複製一遍代碼?git
使用 DTO 能夠清晰的瞭解對象的結構,使用 Pipes(管道)配合 class-validator
還能夠對參數類型進行判斷,還能夠在驗證失敗的時候拋出錯誤信息。github
前兩天發現 NestJS 更新到了 7.0.3(以前是 6.0.0),爲了讓教程更貼合實際,故果斷升級。升級後沒發現什麼大問題,以前的代碼照常運行,若各位讀者發現什麼其餘 Bug ,能夠在 GitHub 上 issues。typescript
GitHub 項目地址,歡迎各位大佬 Star。數據庫
數據傳輸對象(DTO)(Data Transfer Object),是一種設計模式之間傳輸數據的軟件應用系統。數據傳輸目標每每是數據訪問對象從數據庫中檢索數據。數據傳輸對象與數據交互對象或數據訪問對象之間的差別是一個以不具備任何行爲除了存儲和檢索的數據(訪問和存取器)。後端
根據定義,咱們須要在代碼中約定一下 DTO,仍是以註冊接口爲例,先建立 user.dto.ts
簡單定義一下:設計模式
// src/logical/user
export class RegisterInfoDTO {
readonly accountName: string | number;
readonly realName: string;
readonly password: string;
readonly repassword: string;
readonly mobile: number;
}
複製代碼
其實就是輸出了一個相似於聲明接口的 class,代表了參數名和類型,而且是隻讀的。bash
固然,Nest 支持使用 Interface(接口) 來定義 DTO,具體語法能夠瀏覽 TypeScript 官方文檔,不過 Nest 建議使用 Class 來作 DTO(就踩坑經驗而言, Class 確實比 Interface 方便多了),因此 Interface 在這裏就很少介紹了。async
定義好 DTO 後,接下來將演示怎麼和管道配合來驗證參數。函數
管道和攔截器有點像,都是在數據傳輸過程當中的「關卡」,只不過各司其職。
管道有兩個類型:
ValidationPipe
是 Nest.js 自帶的三個開箱即用的管道之一(另外兩個是 ParseIntPipe
和 ParseUUIDPipe
,如今還用不到)。
ValidationPipe
只接受一個值並當即返回相同的值,其行爲相似於一個標識函數,標準代碼以下:
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value;
}
}
複製代碼
每一個管道必須提供 transform()
方法。 這個方法有兩個參數:
value
是當前處理的參數,而 metadata
是其元數據。
簡單介紹完一些概念後,開始實戰,先建立 pipe 文件:
$ nest g pipe validation pipe
複製代碼
這裏咱們還須要安裝兩個依賴包:
$ yarn add class-validator class-transformer -S
複製代碼
而後在 validation.pipe.ts
中編寫驗證邏輯:
// src/pipe/validation.pipe.ts
import { ArgumentMetadata, Injectable, PipeTransform, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { Logger } from '../utils/log4js';
@Injectable()
export class ValidationPipe implements PipeTransform {
async transform(value: any, { metatype }: ArgumentMetadata) {
console.log(`value:`, value, 'metatype: ', metatype);
if (!metatype || !this.toValidate(metatype)) {
// 若是沒有傳入驗證規則,則不驗證,直接返回數據
return value;
}
// 將對象轉換爲 Class 來驗證
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
const msg = Object.values(errors[0].constraints)[0]; // 只須要取第一個錯誤信息並返回便可
Logger.error(`Validation failed: ${msg}`);
throw new BadRequestException(`Validation failed: ${msg}`);
}
return value;
}
private toValidate(metatype: any): boolean {
const types: any[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
複製代碼
綁定管道很是簡單,就和以前使用 Guards 那樣,直接用修飾符綁定在 Controller 上,而後將 body 的類型指定 DTO 便可:
// src/logical/user/user.controller.ts
import { Controller, Post, Body, UseGuards, UsePipes } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '../auth/auth.service';
import { UserService } from './user.service';
import { ValidationPipe } from '../../pipe/validation.pipe';
import { RegisterInfoDTO } from './user.dto'; // 引入 DTO
@Controller('user')
export class UserController {
constructor(private readonly authService: AuthService, private readonly usersService: UserService) {}
// JWT驗證 - Step 1: 用戶請求登陸
@Post('login')
async login(@Body() loginParmas: any) {
...
}
@UseGuards(AuthGuard('jwt'))
@UsePipes(new ValidationPipe()) // 使用管道驗證
@Post('register')
async register(@Body() body: RegisterInfoDTO) { // 指定 DTO類型
return await this.usersService.register(body);
}
}
複製代碼
光有這些還不行,咱們應該增長錯誤提示:
// src/logical/user/user.dto.ts
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
export class RegisterInfoDTO {
@IsNotEmpty({ message: '用戶名不能爲空' })
readonly accountName: string | number;
@IsNotEmpty({ message: '真實姓名不能爲空' })
@IsString({ message: '真實姓名必須是 String 類型' })
readonly realName: string;
@IsNotEmpty({ message: '密碼不能爲空' })
readonly password: string;
@IsNotEmpty({ message: '重複密碼不能爲空' })
readonly repassword: string;
@IsNotEmpty({ message: '手機號不能爲空' })
@IsNumber()
readonly mobile: number;
readonly role?: string | number;
}
複製代碼
上面簡單編寫了一些經常使用的驗證手段,class-validator
裏面有很是多的驗證方法,有興趣的讀者能夠訪問官方文檔去學習:GitHub: class-validator
接下來咱們測試一下,先測試爲空的狀況:
上圖能夠看到 accountName
的 @IsNotEmpty()
已經生效了
注意:class-validator 還提供了一個方法叫 @IsEmpty(),這是表示參數必須爲空,不要搞混了。
再測試參數類型,由於 Postman 的 Body -> x-www-form-urlencoded
默認傳的都是字符串,因此咱們須要稍微修改一下請求參數:
上圖能夠看到 realname
的 @IsString()
已經生效了,再看一下日誌:
至此,入參驗證功能已基本完成,有了這些,咱們就能夠擺脫各類 if - else 來驗證入參了(固然,特殊的,邏輯比較複雜的仍是須要的)。
本篇介紹瞭如何定義 DTO,如何使用 Pipes 管道,以及如何配合 class-validator 進行入參驗證。
定義 DTO 有人可能會以爲好麻煩,直接 any 一把梭不就行了,而後 TypeScript 就逐漸變成了 AnyScript 了。。。。
但若是不擁抱 TypeScript 的特性,那還不如直接用 JavaScript 來寫,這樣還更快(如 Koa、Egg等),定義 DTO 還有一個好處,那就是能夠配合 Swagger 自動生成文檔,而且是可請求的,極大方便了前端閱讀文檔,之後的教程會說明如何操做。
下一篇,將介紹一下如何使用攔截器進行權限認證。
`