跟着作者思路,一起学习Nest的DTO设计模式,教你如何做Post的传递参数验证

1,078 阅读4分钟

DTO设计模式

DTO,即Data Transfer Object,是一种设计模式,用于在不同的软件系统层之间传输数据。举一个最常见的例子,前端通过Post方法向后端传入数据,后端接收到数据后,将数据存储到数据库中。

在NestJS中,DTO用于以下作用:

  • 封装客户端到服务器的数据传输
  • 实现服务器端对数据的验证和转化

Nest实现DTO

在NestJS中,实现DTO主要分为三步:

  1. 定义DTO类
  2. 使用Pips进行数据验证
  3. 如果有需要的话,将数据进行转换

定义DTO类

假设我们需要实现用户登录功能,我们将用户登录注册相关的逻辑都放在一个模块里。为此我们通过nest指令先生成一个CRUD模板

nest g res user

在我们生成的这个模板中,就有dto文件夹

image.png

在这个文件夹里有两个默认生成的文件,分别是create-user.dto.tsupdate-user.dto.ts

image.png

我们首先实现添加用户,即注册功能。为此我们需要先在create-user.dto.ts中编写

export class CreateUserDto {
    user_name: string
    account: string
    password: string
}

我们要求注册的时候,需要提供给用户名、账号和密码。

实现了注册相关的DTO类后,我们就可以在service里面去使用: 在生成的user.service.ts里导入DTO类,然后作为参数传入相关的方法里

import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UserService {
  create(createUserDto: CreateUserDto) {
    return console.log(createUserDto);
  }
}

然后我们启动服务,通过Postman向该接口发送Post请求,看看通过DTO处理后的参数,会包含哪些信息

image.png

我们在传递数据的时候,还传入了没有在DTO类里定义的数据,如hobbyage。那么后端会输出些什么呢?

image.png

我们发现,我们之前写的代码,最终不过是将前端传入的参数按照原样输出了而已,那么DTO有个锤子🔨用呢?不急,我们现在才只是在形式上使用了DTO类而已,还没有接触到DTO的大杀器。

这样我给你举一个例子: 我们建立的数据表只有user_nameaccountpassword三个字段,现在前端给我们传来了五个字段:

  • user_name
  • account
  • password
  • hobby
  • age 有两个字段我们是用不到的,我们要将传来的数据通过prisma写入数据库中。

如果是你,你会如何解决这个问题?

有的人会说,我通过@Body装饰器,获取到body里面的数据,然后解构出需要的数据不就可以了?

create(@Body body){
	let {user_name, accont, password } = body.data.user_name
}

不错👍,是可以

那我如果跟你说,DTO通过配置,可以自动过滤掉无用的字段,即hobbyage不会出现在下面的createUserDto里,是否对DTO有那么小小的心动了?

create(createUserDto: CreateUserDto) {
    return console.log(createUserDto);
}

那如果我再跟你说,DTO通过配置,可以检验前端传来的字段,如果类型错误会返回错误信息给前端。例如: 假设前端发送JSON对象

{
  "user_name": 123,
  "account": "IcicleCrino",
  "password": "myPassword"
}

在这里user_name不是字符串,通过DTO配置后,服务器返回

{
  "statusCode": 400,
  "message": [
    "user_name must be a string"
  ],
  "error": "Bad Request"
}

在返回的报错信息里还包括提示。现在你对DTO是不是更加心动了?

心动不如行动,我们现在就开始学习如何配置

检验数据类型

检验传递给服务器的字段格式的逻辑,在相关的DTO类中编写,这里我们要配置注册DTO类,所以我们在create-user.dto.ts里编写。

对格式的检验是通过Nest提供的装饰器完成,在使用之前,我们需要安装相应的库

npm install class-validator class-transformer

安装完成后,我们还需要应用ValidationPipe来自动验证传入的数据。我们在user.controlelr.ts中添加

import { Controller, Post, Body, } from '@nestjs/common';
import { UserService } from './user.service';
//UsePipes装饰器,用于在类或方法应用管道
//ValidationPipe是NestJS提供的内置管道,用于实现自动验证
import { UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) { }

  @Post()
  //传入ValidationPipe实例
  @UsePipes(new ValidationPipe({
    //将请求体中的数据转化为DTO类的实例
    transform: true,
    //只接受DTO中定义的属性,忽略其他属性
    whitelist: true,
  }))
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }
}

由于我们开启了白名单,前端传递的参数中,如果在DTO类中没有定义,则会被过滤,还是拿之前的例子: image.png

前端传来了两个无效参数

image.png

此时后端接收到的对象中,已经去除了无效参数

然后我们在DTO类中编写类型检验,通过内置装饰器去检验类型:

import { IsString, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
  @IsNotEmpty()
  @IsString()
  user_name: string;

  @IsNotEmpty()
  @IsString()
  account: string;

  @IsNotEmpty()
  @IsString()
  password: string;
}

在这里,我么定义了字段的值必须是string,并且是非空的。

现在我们通过Postman调用接口,将用户名改成数字123

image.png

可以发现,后端返回了错误信息给前端。到这里我们就在NestJS中深入使用了DTO类。