NestJS使用class-validator进行数据验证
作者:乐闻x
前言
在现代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; }
4. 组合验证装饰器
有时可能需要将多个验证规则组合在一起,这时可以使用 @ValidatorConstraint() 来创建自定义验证装饰器。例如:
判断数据库中是否已经存在用户名
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; }
2.验证密码强度
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; }
3.条件验证
根据条件进行验证,可以使用 @ValidateIf 装饰器。
import { ValidateIf, IsNotEmpty, IsEmail } from 'class-validator'; export class UpdateUserDto { @IsEmail() email: string; @ValidateIf(o => o.email) @IsNotEmpty({ message: '新邮件地址不能为空' }) newEmail: string; }
4.使用 @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; }
5.全局验证选项
全局启用验证管道时,可以配置全局验证选项,比如剥离非白名单字段、自动转换类型等。
// 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();
6.动态验证消息
有时可能需要根据具体的验证条件动态生成错误消息,可以使用 ValidationArguments 来实现。
import { IsString, MinLength, ValidationArguments } from 'class-validator'; export class CreateUserDto { @IsString() @MinLength(4, { message: (args: ValidationArguments) => { return `用户名太短了,至少需要 ${args.constraints[0]} 个字符`; }, }) username: string; }
7.@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; }
8.属性分组验证
通过分组,可以在不同情境下验证不同的字段。比如在创建和更新时可能需要验证不同的字段。
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 }; } }
9.仅执行部分属性验证
有时可能需要只验证对象的一部分属性,可以使用 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 中的所有属性,但这些属性都是可选的。这在更新操作中非常有用,因为我们可能只想提供那些需要更新的字段,而不是所有字段。
10.验证消息的国际化
通过使用自定义验证装饰器和消息生成函数,可以实现验证消息的国际化。
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进行数据验证的详细内容,更多关于NestJS数据验证的资料请关注脚本之家其它相关文章!