学习Nest.js(五):使用管道、DTO 验证入参
这章的主要作用是:使用 DTO 可以清晰的了解对象的结构,使用 Pipes(管道)配合
class-validator
还可以对参数类型进行判断,还可以在验证失败的时候抛出错误信息。摆脱 if-else 的恐惧
一、什么是DTO?
数据传输对象(DTO)(Data Transfer Object),是一种设计模式之间传输数据的软件应用系统。数据传输目标往往是数据访问对象从数据库中检索数据。数据传输对象与数据交互对象或数据访问对象之间的差异是一个以不具有任何行为除了存储和检索的数据(访问和存取器)。
根据定义,我们要在代码中约定一下DTO,还是以注册接口为例,先创建user.dto.ts简单定义一下
// src/logical/user
exportclass RegisterInfoDTO {
readonly accountName: string | number;
readonly realName: string;
readonly password: string;
readonly repassword: string;
readonly mobile: number;
}
这个的作用是:输出了一个类似于声明接口的class,表明了参数名和类型,并且是只读的。
nest支持使用Interface(接口)来定义DTO,具体语法可以浏览typescript官方文档,不过nest建议使用class来做DTO。这里只介绍class
定义好DTO后,接下来演示怎么和管道配合来验证参数
二、管道
1.概念
管道和拦截器有点像,都是在数据传输过程中的“关卡”,只不过各司其职。
管道有两个类型:
- 转换:管道将输入数据转换为所需要的数据输出;
- 验证:对输入数据进行验证,如果验证成功继续传递,验证失败则抛出异常;
ValidationPipe 是 nest.js自带的三个开箱即用的管道之一(另外两个是:ParseIntPipe 和 ParseUUIDPipi)
ValidationPipe:只接受一个值并立即返回相同的值,其行为类似于一个标识函数,标准代码如下:
import {PipeTransform,Injectable,ArgumentMetadata} from '@nestjs/common'
@Injectable()
export default ValidationPipe implements PipeTransform{
transform(value:any,metadata:ArgumentMetadata){
return value;
}
每个管道必须提供 transform() 方法。这个方法有两个参数:
- value //当前处理的参数
- metadata //元数据
2.创建管道
开始实际开发:先创建pipe文件
nest g pipe validation pipe
接着安装依赖包:
cnpm i 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()
exportclass 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}`);
thrownew BadRequestException(`Validation failed: ${msg}`);
return value;
private toValidate(metatype: any): boolean {
const types: any[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
3.绑定管道
绑定管道非常简单,就和之前使用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')
exportclass 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类型
returnawaitthis.usersService.register(body);
}
4.完善错误信息
增加错误信息
// src/logical/user/user.dto.ts
import { IsNotEmpty, IsNumber, IsString } from'class-validator';
exportclass 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;