NestJS 项目中如何使用 class-validator 进行数据验证

news2024/11/13 10:38:30

前言

在现代Web开发中,数据验证是必不可少的一环,它不仅能够确保数据的准确性,还能提高系统的安全性。在使用NestJS框架进行项目开发时,class-validator与class-transformer这两个库为我们提供了方便的数据验证解决方案。
本文将通过详细的步骤和实战技巧,带大家掌握如何在NestJS中使用class-validator进行数据验证。通过这篇文章,你将能够学会如何使用class-validator优雅的实现数据验证,以及11条实战中常用的验证技巧,提高项目的数据校验能力。

使用步骤

第一步:安装 class-validator 和 class-transformer

要使用 class-validator,需要安装两个库:class-validator 和 class-transformer。
npm install class-validator class-transformer

第二步:创建 DTO(数据传输对象)

在 NestJS 中,通常使用 DTO(Data Transfer Object)来定义请求数据的结构。首先,需要创建一个用于用户注册的 DTO 类,并使用 class-validator 的装饰器来定义验证规则。

// src/user/dto/create-user.dto.ts
import { IsString, IsEmail, IsNotEmpty, Length } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  @Length(4, 20)
  username: string;

  @IsEmail()
  email: string;

  @IsString()
  @IsNotEmpty()
  @Length(8, 40)
  password: string;
}

在这个 DTO 中,定义了三个字段:username、email 和 password,并使用 class-validator 的装饰器指定了验证规则。

第三步:使用管道验证数据

接下来,需要在控制器中使用 DTO,并通过 NestJS 的管道(Pipes)来验证传入的数据。

// src/user/user.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('user')
export class UserController {
  @Post('register')
  async register(@Body() createUserDto: CreateUserDto) {
    // 处理注册逻辑
    return { message: 'User registered successfully', data: createUserDto };
  }
}

在这个例子中,在 register 方法中使用了 @Body() 装饰器来获取请求体,并传入了 CreateUserDto。NestJS 会自动验证该 DTO,如果验证失败,将抛出异常并返回适当的错误响应。

第四步:全局启用验证管道

为了更方便地管理,可以全局启用验证管道,这样所有的 DTO 验证都会自动进行。

// src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

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

bootstrap();

在 main.ts 文件中,使用 ValidationPipe 全局启用了验证管道。这样一来,无论在哪个控制器中使用 DTO,NestJS 都会自动进行数据验证。当然也可以仅对某些控制器开启验证管道,详情参考下方实战技巧。

实战使用技巧

1. 局部验证管道

可以为特定的路由或控制器方法配置验证管道,而无需全局启用。这样可以在不同的场景下灵活使用不同的验证规则。

import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('user')
export class UserController {
  @Post('register')
  @UsePipes(new ValidationPipe({
    transform: true,
    whitelist: true,
    forbidNonWhitelisted: true,
  }))
  async register(@Body() createUserDto: CreateUserDto) {
    // 处理注册逻辑
    return { message: 'User registered successfully', data: createUserDto };
  }
}

2. 自定义错误消息

class-validator 允许为每个验证规则定义自定义错误消息。例如:

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

export class CreateUserDto {
  @IsString({ message: '用户名必须是字符串' })
  @IsNotEmpty({ message: '用户名不能为空' })
  @Length(4, 20, { message: '用户名长度必须在4到20个字符之间' })
  username: string;

  @IsEmail({}, { message: '邮箱格式不正确' })
  email: string;
  
  @IsString({ message: '密码必须是字符串' })
  @IsNotEmpty({ message: '密码不能为空' })
  @Length(8, 40, { message: '密码长度必须在8到40个字符之间' })
  password: string;
}

3. 嵌套对象验证

如果 DTO 中包含嵌套对象,可以使用 @ValidateNested() 装饰器进行验证。例如:

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

class AddressDto {
  @IsString()
  @IsNotEmpty()
  street: string;

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

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

  @ValidateNested()
  @Type(() => AddressDto)
  address: AddressDto;
}

3. 组合验证装饰器

有时可能需要将多个验证规则组合在一起,这时可以使用 @ValidatorConstraint() 来创建自定义验证装饰器。例如:

  1. 判断数据库中是否已经存在用户名
import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';

@ValidatorConstraint({ async: false })
export class IsUsernameUniqueConstraint implements ValidatorConstraintInterface {
  validate(username: any) {
    // 这里可以添加验证逻辑,例如查询数据库
    return true; // 如果验证通过返回 true
  }
}

export function IsUsernameUnique(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      constraints: [],
      validator: IsUsernameUniqueConstraint,
    });
  };
}

// 使用自定义装饰器
export class CreateUserDto {
  @IsUsernameUnique({ message: '用户名已存在' })
  username: string;
}
  1. 验证密码强度
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';
export function IsStrongPassword(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'isStrongPassword',
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          return /(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}/.test(value);
        },
        defaultMessage(args: ValidationArguments) {
          return '密码必须包含大小写字母、数字和特殊字符,并且至少8个字符长';
        },
      },
    });
  };
}
export class ChangePasswordDto {
  @IsStrongPassword({ message: '密码不符合强度要求' })
  newPassword: string;
}
  1. 条件验证
    根据条件进行验证,可以使用 @ValidateIf 装饰器。
import { ValidateIf, IsNotEmpty, IsEmail } from 'class-validator';
export class UpdateUserDto {
  @IsEmail()
  email: string;
  @ValidateIf(o => o.email)
  @IsNotEmpty({ message: '新邮件地址不能为空' })
  newEmail: string;
}
  1. 使用 @Matches 进行正则表达式验证
    使用 @Matches 装饰器,可以验证字符串是否与指定的正则表达式匹配。
import { Matches } from 'class-validator';
export class ChangePasswordDto {
  @Matches(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}/, { message: '密码必须包含大小写字母、数字和特殊字符,并且至少8个字符长' })
  newPassword: string;
}
  1. 全局验证选项
    全局启用验证管道时,可以配置全局验证选项,比如剥离非白名单字段、自动转换类型等。
// src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true, // 剥离非白名单字段
    forbidNonWhitelisted: true, // 禁止非白名单字段
    transform: true, // 自动转换类型
  }));
  await app.listen(3000);
}
bootstrap();
  1. 动态验证消息
    有时可能需要根据具体的验证条件动态生成错误消息,可以使用 ValidationArguments 来实现。
import { IsString, MinLength, ValidationArguments } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(4, {
    message: (args: ValidationArguments) => {
      return `用户名太短了,至少需要 ${args.constraints[0]} 个字符`;
    },
  })
  username: string;
}
  1. @Validate 自定义验证逻辑
    如果内置装饰器无法满足需求,可以使用 @Validate 装饰器添加自定义验证逻辑。
import { Validate, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';

@ValidatorConstraint({ name: 'customText', async: false })
class CustomTextConstraint implements ValidatorConstraintInterface {
  validate(text: string, args: ValidationArguments) {
    return text.startsWith('prefix_'); // 任何自定义逻辑
  }

  defaultMessage(args: ValidationArguments) {
    return '文本 ($value) 必须以 "prefix_" 开头';
  }
}

export class CustomTextDto {
  @Validate(CustomTextConstraint)
  customText: string;
}
  1. 属性分组验证
    通过分组,可以在不同情境下验证不同的字段。比如在创建和更新时可能需要验证不同的字段。
import { IsString, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsNotEmpty({ groups: ['create'] })
  username: string;

  @IsString()
  @IsNotEmpty({ groups: ['create', 'update'] })
  password: string;
}

// 使用时指定组
import { ValidationPipe } from '@nestjs/common';

const createUserValidationPipe = new ValidationPipe({ groups: ['create'] });
const updateUserValidationPipe = new ValidationPipe({ groups: ['update'] });

在控制器中使用不同的管道进行验证:
import { Controller, Post, Put, Body, UsePipes } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { createUserValidationPipe, updateUserValidationPipe } from './validation-pipes';

@Controller('user')
export class UserController {
  @Post('create')
  @UsePipes(createUserValidationPipe)
  async createUser(@Body() createUserDto: CreateUserDto) {
    // 处理创建用户逻辑
    return { message: 'User created successfully', data: createUserDto };
  }

  @Put('update')
  @UsePipes(updateUserValidationPipe)
  async updateUser(@Body() updateUserDto: CreateUserDto) {
    // 处理更新用户逻辑
    return { message: 'User updated successfully', data: updateUserDto };
  }
}
  1. 仅执行部分属性验证
    有时可能需要只验证对象的一部分属性,可以使用 PartialType 来实现。
import { PartialType } from '@nestjs/mapped-types';

export class UpdateUserDto extends PartialType(CreateUserDto) {}
以下是如何在控制器中使用 UpdateUserDto。
// src/user/user.controller.ts
import { Controller, Post, Put, Body, Param } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('user')
export class UserController {
  @Post('create')
  async createUser(@Body() createUserDto: CreateUserDto) {
    // 处理创建用户逻辑
    return { message: 'User created successfully', data: createUserDto };
  }
  @Put('update/:id')
  async updateUser(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    // 处理更新用户逻辑
    return { message: 'User updated successfully', data: updateUserDto };
  }
}

在这个示例中,UpdateUserDto 继承自 PartialType(CreateUserDto),这意味着 UpdateUserDto 包含 CreateUserDto 中的所有属性,但这些属性都是可选的。这在更新操作中非常有用,因为我们可能只想提供那些需要更新的字段,而不是所有字段。

  1. 验证消息的国际化
    通过使用自定义验证装饰器和消息生成函数,可以实现验证消息的国际化。
import { IsString, IsNotEmpty, Length, ValidationArguments } from 'class-validator';
import { i18n } from 'i18next'; // 假设在项目中使用 i18n

export class CreateUserDto {
  @IsString()
  @IsNotEmpty({ message: (args: ValidationArguments) => i18n.t('validation.usernameRequired') })
  @Length(4, 20, { message: (args: ValidationArguments) => i18n.t('validation.usernameLength', { min: 4, max: 20 }) })
  username: string;
}

总结

使用 class-validator 结合 NestJS,可以让轻松地在应用中进行数据验证,不仅提高了代码的可读性,还保证了数据的准确性和安全性。通过本文的介绍和技巧,大家应该大致掌握了如何在 NestJS 中使用 class-validator 进行数据验证,大家都在项目中实践起来吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2237247.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

财务源码 财务软件 SaaS 云财务

🔍 专业财务源码,助您快速开展财务管理!📈 🎯 我们提供一系列高质量、可定制、易于使用的财务源码,帮助您快速搭建强大的财务管理系统。无论是小型企业、中型企业,还是个人用户,我们…

数据流图,学习笔记

目录 一、数据流图的基本元素 外部实体(External Entity) 加工(Process) 数据存储(Data Store) 数据流(Data Flow) 二、数据流图的层次结构 顶层数据流图 中层数据流图 底层…

docker镜像文件导出导入

1. 导出容器(包含内部服务)为镜像文件(docker commit方法) 原理:docker commit命令允许你将一个容器的当前状态保存为一个新的镜像。这个新镜像将包含容器内所有的文件系统更改,包括安装的软件、配置文件等…

Android中桌面小部件framework层使用到的设计模式

在Android中,桌面小部件(App Widget)的Framework层采用了多种设计模式,以实现模块化、可维护性和高效的交互。 以下是Android桌面小部件Framework层中常用的设计模式及其具体应用: 1. 观察者模式(Observe…

7.《双指针篇》---⑦三数之和(中等偏难)

题目传送门 方法一:双指针 1.新建一个顺序表用来返回结果。并排序数组。 2.for循环 i 从第一个数组元素遍历到倒数第三个数。 3.如果遍历过程中有值大于0的则break; 4.定义左右指针,以及target。int left i 1, right n - 1; int target -nums[i];…

DBeaver工具连接Hive

DBeaver工具连接Hive 首先解压安装包dbeaver-ce-latest-x86_64-setup.zip,并安装dbeaver-ce-latest-x86_64-setup.exe; 安装Kerberos客户端4.1-amd64.msi; 查看集群节点/etc/hosts文件内容,并追加到C:\Windows\System32\drivers\etc\hosts; 下载集群用户keytab文件,并解压…

从零开始 blender插件开发

blender 插件开发 文章目录 blender 插件开发环境配置1. 偏好设置中开启相关功能2. 命令行打开运行脚本 API学习专有名词1. bpy.data 从当前打开的blend file中,加载数据。2. bpy.context 可用于获取活动对象、场景、工具设置以及许多其他属性。3. bpy.ops 用户通常…

深入理解JVM

文章目录 1. JVM内存区域划分2. JVM中类加载过程类加载(1)类加载的基本流程(2)双亲委派模型 《深入理解java虚拟机》 在这本书前,面试官对于JVM也不是很了解。 这本书主要还是写个一下开发 JVM 的人。 1. JVM内存区域…

c# 开发web服务 webserver

024-11-10<<<<<<<<<<<<<<<<<<<<<<<<<< 开始插件前Cyber_CallWeb acajax_dac_database_viewer 2024-11-10<<<<<<<<<<<<<<<<<<<<…

WPS 默认模板修改

重装系统把word自定义样式搞没了&#xff0c;安装office时间太长&#xff0c;转战wps 解决方案 打开wps 点击【新建】word空白文档 设置修改你自己的样式 点击文件–另存为–Microsoft Word 带宏的模板文件&#xff08;*.dotm&#xff09; 另存路径为如下&#xff1a; 查…

使用vite构建一个react网站,并部署到Netlify上

这篇教程中&#xff0c;我会教你如何用vite快速构建一个react网站&#xff0c;并把网站免费部署到Netlify上&#xff0c;让别人可以经由网址访问你的react网站。 1. 使用vite构建基础框架 npm create vitelatestcd vite-project npm install npm run dev2. 网站内容设计 3. 构…

GPT-5 一年后发布?对此你有何期待?

GPT-5 一年后发布?对此你有何期待? 在最新技术的洪流中,GPT-5即将登场。你是否在思考,它将为我们的生活和工作带来哪些变革?接下来的探索,或许可以启发你对未来的想象。让我们一起深入这场关于未来AI语言模型的讨论。 一、技术上的提升 1.1 更强的语言理解能力 想象一…

MPC5744P——UART通信

简介 快速通过官方的example工程跑通MPC5744P的UART通信。 一、软件工程 点击File->New->S32DS Project from Example&#xff0c;打开示例程序窗口。 选择MPC5744P->LINFlexD_UART_MPC5744P&#xff0c;点击Finish&#xff0c;创建UART的示例工程。 工程创建成功&…

安当ASP系统:适合中小企业的轻量级Radius认证服务器

安当ASP&#xff08;Authentication Service Platform&#xff09;身份认证系统是一款功能强大的身份认证服务平台&#xff0c;特别适用于中小企业。其中&#xff0c;简约型Radius认证服务器是安当ASP系统中的一个重要组成部分。以下是对该系统的详细介绍&#xff1a; 一、主要…

「Mac畅玩鸿蒙与硬件30」UI互动应用篇7 - 简易计步器

本篇将带你实现一个简易计步器应用&#xff0c;用户通过点击按钮增加步数并实时查看步数进度&#xff0c;目标步数为 10000 步。该项目示例展示了如何使用 Progress 组件和 Button 组件&#xff0c;并结合状态管理&#xff0c;实现交互式应用。 关键词 UI互动应用计步器Button…

(64)使用RLS自适应滤波器进行预测的MATLAB仿真

文章目录 前言一、仿真说明四、MATLAB仿真代码五、仿真结果总结与后续 前言 RLS&#xff08;递归最小二乘&#xff09;自适应滤波器是一种用于信号处理的算法&#xff0c;其原理基于最小二乘法。在时间序列分析中&#xff0c;RLS滤波器可以用于预测信号的下一个值。本文以股票…

Python小白学习教程从入门到入坑------第二十九课 访问模式文件定位操作(语法进阶)

一、访问模式 模式可做操作若文件不存在是否覆盖r只能读报错-r可读可写报错是w只能写创建是w可读可写创建是a只能写创建否&#xff0c;追加写a可读可写创建否&#xff0c;追加写 1.1 r r&#xff1a;只读模式(默认模式)&#xff0c;文件必须存在&#xff0c;不存在就会报错…

学习记录:js算法(八十九):电话号码的字母组合

文章目录 电话号码的字母组合思路一 电话号码的字母组合 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 如图 示例 1&…

【机器学习】均方误差(MSE:Mean Squared Error)

均方误差&#xff08;Mean Squared Error, MSE&#xff09;是衡量预测值与真实值之间差异的一种方法。在统计学和机器学习中&#xff0c;MSE 是一种常见的损失函数&#xff0c;用于评估模型的预测准确性。 均方误差的定义 假设有一组真实值 ​ 和模型预测的对应值 ​。均方误…

Uniapp安装Pinia并持久化(Vue3)

安装pinia 在uni-app的Vue3版本中&#xff0c;Pinia已被内置&#xff0c;无需额外安装即可直接使用&#xff08;Vue2版本则内置了Vuex&#xff09;。 HBuilder X项目&#xff1a;直接使用&#xff0c;无需安装。CLI项目&#xff1a;需手动安装&#xff0c;执行yarn add pinia…