相关文章推荐
痴情的铁链  ·  batch for 命令 - CSDN文库·  2 周前    · 
安静的茄子  ·  Window: fetch() ...·  3 周前    · 
才高八斗的西红柿  ·  if...else - ...·  2 月前    · 

上一篇介绍了如何使用中间件、拦截器、过滤器打造日志系统,接下来将介绍后端永远绕不过去的痛:参数验证。

你是否曾经为了验证参数,写了一大堆 if - else ?然后还要判断各种参数类型?相似的结构在不同的方法里判断,却又要复制一遍代码?

使用 DTO 可以清晰的了解对象的结构,使用 Pipes(管道)配合 class-validator 还可以对参数类型进行判断,还可以在验证失败的时候抛出错误信息。

前两天发现 NestJS 更新到了 7.0.3(之前是 6.0.0),为了让教程更贴合实际,故果断升级。升级后没发现什么大问题,之前的代码照常运行,若各位读者发现什么其他 Bug ,可以在 GitHub 上 issues。

GitHub 项目地址 [1] ,欢迎各位大佬 Star。

一、什么是 DTO?

数据传输对象(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,表明了参数名和类型,并且是只读的。

当然,Nest 支持使用 Interface(接口) 来定义 DTO,具体语法可以浏览 TypeScript 官方文档,不过 Nest 建议使用 Class 来做 DTO(就踩坑经验而言, Class 确实比 Interface 方便多了),所以 Interface 在这里就不多介绍了。

定义好 DTO 后,接下来将演示怎么和管道配合来验证参数。

1. 概念

管道和拦截器有点像,都是在数据传输过程中的“关卡”,只不过各司其职。

管道有两个类型:

  • 转换:管道将输入数据转换为所需的数据输出;

  • 验证:对输入数据进行验证,如果验证成功继续传递,验证失败则抛出异常;

ValidationPipe 是 Nest.js 自带的三个开箱即用的管道之一(另外两个是 ParseIntPipeParseUUIDPipe,现在还用不到)。

ValidationPipe 只接受一个值并立即返回相同的值,其行为类似于一个标识函数,标准代码如下:

import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
 

每个管道必须提供 transform() 方法。这个方法有两个参数:

  • value

  • metadata

value 是当前处理的参数,而 metadata 是其元数据。

2. 创建管道

简单介绍完一些概念后,开始实战,先创建 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);
 

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')
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);
 

4. 完善错误提示

光有这些还不行,我们应该增加错误提示:

// 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[2]

接下来我们测试一下,先测试为空的情况:

上图可以看到 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 自动生成文档,并且是可请求的,极大方便了前端阅读文档,以后的教程会说明如何操作。

下一篇,将介绍一下如何使用拦截器进行权限认证。

GitHub 项目地址: https://github.com/SephirothKid/nest-zero-to-one

[2]

GitHub: class-validator: https://github.com/typestack/class-validator

● Nest.js 从零到壹系列(一):项目创建&路由设置&模块● Nest.js 从零到壹系列(二):数据库的连接● Nest.js 从零到壹系列(三):使用 JWT 实现单点登录
 

·END·

汇聚精彩的免费实战教程

关注公众号回复 z 拉学习交流群

喜欢本文,点个“在看”告诉我

本文由图雀社区认证作者布拉德特皮写作而成,点击阅读原文查看作者掘金链接,感谢作者的优质输出,让我们的技术世界变得更加美好????前言上一篇介绍了如何使用中间件、拦截器、过滤器打造日志...
一个渐进式的框架,用于构建高效且可扩展的服务器端应用程序。 该项目基于构建。 它使用将markdown格式的源文档编译为已发布的格式。 存储库包含源代码(Nest官方文档)。 安装项目依赖项,并使用以下终端命令启动本地服务器: $ npm install $ npm run start 导航到 。 所有页面均以编写,并位于content目录中。 运行npm run build来构建项目。 构建工件将存储在dist/目录中。 要在监视模式下运行构建,请运行npm run build:watch 。 任何内容更改都将重新编译和重建,并将内容提供到 。 使用npm run build:prod进行生产构建。 Nest是MIT许可的开源项目。 得益于赞助商和出色支持者的支持,它可以发展壮大。 如果您想加入他们,请。 网站-https: Nest已。
项目拉下来后,运行时会报错找不到 config/db.ts,这个文件需要自己创建,并配置好数据库信息。具体教程可以参考我的掘金博客: 分支 use-redis 新增了 Redis 功能,为照顾没学到第 8 课的读者,在不影响 master 分支的情况下,单独开的分支。具体教程可以参考我的掘金博客: 完整教程可以参考我的掘金博客集: A progressive framework for building efficient and scalable server-side applications, heavily inspired by . Description Nest framework TypeScript starter repository. Installation $ npm install Running the app # development $ npm
使用JavaScript(Node.js,TypeScript,Nest.js,React)的信号量演示CI / CD管道 示例应用程序和CI / CD管道显示了如何在Semaphore 2.0上运行JavaScript项目。 项目由基于Nest.js的Node.js服务器和React客户端组成。 代码是用TypeScript编写的。 信号量上的CI / CD 分叉此存储库并使用它来。 CI管道如下所示: 示例管道包含4个块: 安装依赖项安装并缓存所有npm依赖项 运行皮棉运行tslint以检查项目文件的代码样式 运行单元测试运行单元测试 运行端到端测试 通过cypress在客户端上运行E2E测试。 通过服务器上的玩笑来运行E2E测试。 然后,如果所有检查都OK,我们开始构建管道。 它由一个块组成 使用create-react-app sctipts构建客户端-构建客户端应用 构建服务器-构建容器并将其推入Google存储库 然后,在构建了应用程序之后,我们开始部署管道。 它还包括一个用于客户端的块和两个用于服务器的块。 如您所见,客户端和服务器的部署管道仅取决于它们
1、获取post请求的数据用@Body()参数装饰器获取数据,get用参数装饰器@Query。 另外@Param()获取Params里的东西 2、在获取参数时,有事会需要定义请求数据格式,可以按照下面创建createPostDto类的方式来约束数据。 import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common'; import { ApiOperation, ApiTags } from '@nestjs/swagger
NestJS入门controller、DTO、providers、module。 官方API地址https://docs.nestjs.com/ Nest(或NestJS)是一个用于构建高效,可扩展的Node.js服务器端应用程序的框架。也可以在Nest使用express框架的扩展 安装(官方也有推荐) npm i -g @nestjs/cli //全局安装脚手架 nest new proj...
2、比如在cat的目录下创建一个dto的文件夹,创建一个create.cat.dto.ts的文件 import { IsString, IsInt, MinLength, MaxLength } from 'class-validator'; export c... OpenAPI (Swagger) 相关 npm install --S @nestjs/swagger swagger-ui-express // pic.controller.ts import { ApiTags, ApiProperty, ApiOperation } from '@nestjs/swagger'; // 定义dto export class GetPicDto { @ApiProperty({ @Post() async create(@Body() createCatDto: CreateCatDto) { throw new HttpException('Forbidden', HttpStatus.FORBIDDEN); // ...
Nest.js 中实现接口的权限控制可以通过自定义 Guard 实现。Guard 是 Nest.js 中用于控制路由访问权限的模块,它可以在请求路由处理之前对请求进行拦截并进行验证。以下是一个简单的示例: 首先,你需要创建一个 AuthGuard 类,用于对请求进行验证: ```typescript import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class AuthGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { // 在这里进行权限验证,如果通过则返回 true,否则返回 false return true; 然后,在需要进行权限控制的路由上使用 AuthGuard: ```typescript import { Controller, Get, UseGuards } from '@nestjs/common'; import { AuthGuard } from './auth.guard'; @Controller('users') export class UsersController { @Get() @UseGuards(AuthGuard) findAll(): string { return 'This action returns all users'; 在这个示例中,我们在 `findAll` 方法上使用了 `@UseGuards(AuthGuard)` 装饰器,表示需要通过 AuthGuard 进行权限验证。当请求到达 `findAll` 方法时,Nest.js 会先调用 AuthGuard 的 `canActivate` 方法进行权限验证。如果 `canActivate` 返回 true,则请求会继续被处理;否则,请求将被拒绝。 你可以在 `canActivate` 方法中实现任意的权限验证逻辑,例如验证用户是否拥有访问该接口的权限、验证请求中的 token 是否合法等等。如果验证成功,`canActivate` 方法应该返回 true;否则,返回 false。
console.log(`JWT验证 - Step 4: 被守卫调用`); console.log(payload) return { candidateId: payload.sub, fullname: payload.fullname, mobile: payload.mobile }; JWT验证 - Step 4: 被守卫调用 fullname: 'test001', sub: 77, mobile: '13111111111', iat: 1614243534, exp: 1614272334 [/code] 字节跳动最爱考的前端面试题:JavaScript 基础 欲寄彩笺兼尺素,山长水阔知何处。——晏殊《鹊踏枝》 字节跳动最爱考的前端面试题:JavaScript 基础 大佬,咋整啊: let 是不存在变量提升的,如果有变量提升那输出结果应该是undefined而不是报错