管道与验证
- 管道:后端是服务用户的,对于用户提交的数据,对于用户传递的五花八门的数据,我们需要借助管道来进行限定,限定用户传递某种内容。还有一种情况,当我们通过网络请求的时候,传递过来的数字1是一个字符串类型,但是
ts
是严格类型要求的,我们要求这个数字必须是数值类型,我们这时可以使用管道,将这个传递过来的内容的类型进行转化,总之,管道是对进入的数据进行处理的 - 验证:对于用户提交的数据,我们需要进行验证,如内容长度太长,或者内容为空,这些情况是不会通过验证的,也就存储不到数据库中
我们可以使用Apifox
在线工具来测试我们的接口,我们先创建一个项目,在项目中配置测试环境,本地开发测试环境可以设置为http://localhost:3000
,新建一个接口,选择测试环境,输入路径进行接口的请求,其中/
表示根路径
请求得到的内容和在网页中展示的内容是一致的
对于项目中的根控制器app.controller.ts
:
import { Controller, Get, Param } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get(':id')
getHello(@Param('id') id: number) {
return id;
}
}
对于
@Get()
装饰器,是可以添加参数的,如@Get(':id')
,与vue
的写法类似对于
getHello()
具体函数,可以使用@Param()
装饰器来接收这个id
,同时将这个值赋值给为数值变量类型的id
最后将
id
进行返回在接口测试器中发送
http://localhost:3000/1
,就可以接收到这个参数1,同时在网页中显示
但是如果我们对这个返回的参数进行其类型的打印:console.log(typeof id)
,我们会发现这个id
的类型是字符串类型,如果我们使用这个id
去匹配数据库表中的对应为数值类型的字段,就会显示类型报错:
import { Controller, Get, Param } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { AppService } from './app.service';
@Controller()
export class AppController {
prisma: PrismaClient;
constructor(private readonly appService: AppService) {
this.prisma = new PrismaClient();
}
@Get(':id')
getHello(@Param('id') id: number) {
return this.prisma.article.findUnique({
where: {
id: id,
}
});
}
}
我们尝试进行查询,在测试环境中发送
/1
,后端就会报错:Invalid value for argument
id: invalid digit found in string. Expected big integer String.
,说明:传入的是一个字符串类型,但是匹配需要的是数值类型,因此,我们需要使用管道来解决这个问题
转换数值管道
创建管道:nest g pi new --no-spec
,执行后就会在src/new
文件中创建管道文件new.pipe.ts
在根控制器app.controller.ts
中使用管道:
import { Controller, Get, Param } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { AppService } from './app.service';
import { NewPipe } from './new/new.pipe';
@Controller()
export class AppController {
prisma: PrismaClient;
constructor(private readonly appService: AppService) {
this.prisma = new PrismaClient();
}
@Get(':id')
getHello(@Param('id', NewPipe) id: number) {
return this.prisma.article.findUnique({
where: {
id: +id,
}
});
}
}
@Param('id', NewPipe)
:当数据进来的时候,就会使用跟在后面的NewPipe
管道,使用管道先进行数据的处理,在管道中我们需要进行拦截操作
编辑new.pipe.ts
文件,使管道对传入的内容进行类型的转换:如果要求的类型是数值类型,就将其转换为数值类型,如果要求的类型不是数值类型,就不做任何处理,直接将值进行返回:
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
@Injectable()
export class NewPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return metadata.metatype == Number ? +value : value;
}
}
管道中有两个参数:
value
:用户传入的参数值
metadata
:原数据,包含了数据的额外的信息
{ metatype: [Function: Number], type: 'param', data: 'id' }
metatype
:数据类型,构造函数是一个Number
的数据类型type
:请求的类型,param
表示地址栏参数data
:接收的参数,接收id
的值这样就可以解决传入类型与数据库匹配字段类型不同的问题了,在接口测试工具中输入
/1
,就可以返回文章表中id
为1的这一条数据的全部内容了
对于一些高频使用的管道,系统都有提供,如:将数据类型转化为数值类型的管道ParseIntPipe
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { AppService } from './app.service';
@Controller()
export class AppController {
prisma: PrismaClient;
constructor(private readonly appService: AppService) {
this.prisma = new PrismaClient();
}
@Get(':id')
getHello(@Param('id', ParseIntPipe) id: number) {
return this.prisma.article.findUnique({
where: {
id: id,
}
});
}
}
效果和我们自己写的
NewPipe
管道相同
管道的定义方式
管道定义方式一共有以下几种:
控制器
将管道注册在控制器层面,这样就会影响控制器内部所有的方法:
tsimport { Controller, Get, Param } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; import { AppService } from './app.service'; import { NewPipe } from './new/new.pipe'; @Controller() @UsePipes(NewPipe) export class AppController { prisma: PrismaClient; constructor(private readonly appService: AppService) { this.prisma = new PrismaClient(); } @Get(':id') getHello(@Param('id') id: number) { return this.prisma.article.findUnique({ where: { id: +id, } }); } }
结果还是正常的,可以根据输入的参数,获取对数据库中对应匹配的值
控制器方法
控制器方法层面,我们可以进行以下方式的注册:
tsimport { Controller, Get, Param } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; import { AppService } from './app.service'; import { NewPipe } from './new/new.pipe'; @Controller() export class AppController { prisma: PrismaClient; constructor(private readonly appService: AppService) { this.prisma = new PrismaClient(); } @Get(':id') @UsePipes(NewPipe) getHello(@Param('id') id: number) { return this.prisma.article.findUnique({ where: { id: +id, } }); } }
结果还是正常的,可以根据输入的参数,获取对数据库中对应匹配的值
方法参数
方法参数层面进行管道的注册,将管道注册在对应的参数后面
tsimport { Controller, Get, Param } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; import { AppService } from './app.service'; import { NewPipe } from './new/new.pipe'; @Controller() export class AppController { prisma: PrismaClient; constructor(private readonly appService: AppService) { this.prisma = new PrismaClient(); } @Get(':id') getHello(@Param('id', NewPipe) id: number) { return this.prisma.article.findUnique({ where: { id: +id, } }); } }
结果还是正常的,可以根据输入的参数,获取对数据库中对应匹配的值
一般情况对于参数,我们管道注册在参数后面即可:
@Param('id', NewPipe)
模块
我们也可以将管道放到模块中进行注册,我们在根模块
app.module.ts
中进行管道的注册:tsimport { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { APP_PIPE } from '@nestjs/core'; import { NewPipe } from './new/new.pipe'; @Module({ imports: [], controllers: [AppController], providers: [AppService, { // 定义管道 provide: APP_PIPE,// 起一个NestJs要使用的管道名称,内部要APP_PIPE名称的提供者 useClass: NewPipe }], }) export class AppModule {}
可以在管道模块中进行
@Inject
依赖的注入全局管道
在
main.ts
文件中进行全局管道的注册:tsimport { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { NewPipe } from './new/new.pipe'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new NewPipe()); // 全局进行管道的注册 await app.listen(process.env.PORT ?? 3000); } bootstrap();
全局注册后,我们就可以在任何地方去使用这个管道(不需要任何导入),但是在全局文件中进行注册是不能进行后续的依赖注入的
系统提供的常用管道
有些管道的逻辑在开发中是非常常用的,因此,系统将这些管道分装好,直接提供给我们进行使用
系统提供的常用管道有:
ParseIntPipe
:将参数的数据类型转化为数值类型内置管道
ParseIntPipe
是用于转换number
类型中的“整数”的。 如果说想转的值是浮点型或者NaN
,像这些值虽然也属于number
类型,但由于它们不属于“整数”,所以ParseIntPipe
会抛出异常ParseFloatPipe
:将参数的数据类型转化为浮点数类型ParseBoolPipe
:将参数的数据类型转化为布尔类型ParseArrayPipe
:将参数的数据类型转化为数组类型ParseUUIDPipe
:将参数的数据类型转化为UUID
类型ParseEnumPipe
:将参数的数据类型转化为枚举类型ParseFilePipe
:将参数的数据类型转化为文件类型ParseDatePipe
:将参数的数据类型转化为日期类型DefaultValuePipe
:默认值管道,如果没有传递参数,可以使用默认值管道,使用默认值ts@Get() getHello(@Param('id', new DefaultValuePipe(1)) id: number) { return this.prisma.article.findUnique({ where: { id: +id, } }); }
当
id
没有进行传参的时候,就使用默认值管道中的默认值1ValidationPipe
:校验时必须要在Query
,Body
等装饰器上加上ValidationPipe
管道才能对请求数据进行验证
验证
对于用户提交上来的数据,我们需要使用管道进行对其内容的验证,保证其内容是符合规定的,对于不符合规定的数据,我们要对其进行拦截,使其不能往数据库中进行写入
根控制器app.controller.ts
文件:
import { Body, Controller, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('store')
add(@Body() dto: Record<string, any>) {
console.log(dto);
return dto;
}
}
使用装饰器
@Body()
来接收数据dto
@Post('store')
中@Post()
装饰器内部的字符串是地址,名称可以任意的取在接口测试工具中,可以添加一个
POST
接口,进行数据的发送,选择测试环境,在地址中输入/store
选择
Body
的数据方式:
我们还可以在命令终端以命令行的形式进行数据的发送:
curl -H "Content-Type:application/json" -X POST -d '{"title":"NestJs的使用","content":"管道的验证操作"}' http://localhost:3000/store
-H
表示指定头信息"Content-Type:application/json"
:告诉后端,我们发送的是一个JSON
数据-X
:表示要发送的请求方式,指定的是POST
请求-d
:表示要传递过去的数据,后面接传递过去的数据,数据要使用JSON
格式,键和值都要用引号包裹发送成功后,在后端就可以看到数据:
上述情况下,我们如果发送的数据没有内容,也是可以进行接收的,这个在实际的项目中就不合理,我们需要设计管道来进行验证,如果title
或者content
的内容为空时,我们会抛出异常
创建一个管道nest g pi request --no-spec
,用来对用户提交的内容进行验证,管道验证的内容为:
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
@Injectable()
export class RequestPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
if (!value.title) {
throw new BadRequestException('标题不能为空');
}
if (!value.content) {
throw new BadRequestException('内容不能为空');
}
return value;
}
}
value
:接收的对象数据
metadata
:原数据包含如下的内容
{ metatype: [Function: Object], type: 'body', data: undefined }
metatype
:数据类型,构造函数是一个Object
的数据类型,定义的是一个对象type
:接收的类型,POST
数据,类型是body
data
:接收的参数,对于对象,没有指定某一个KEY
,所以返回的是空
BadRequestException
:错误请求异常
修改后的根控制器app.controller.ts
文件,引入了管道进行验证:
import { Body, Controller, Post } from '@nestjs/common';
import { AppService } from './app.service';
import { RequestPipe } from './request/request.pipe';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('store')
add(@Body(RequestPipe) dto: Record<string, any>) {
console.log(dto);
return dto;
}
}
当发送内容为空时,会抛出异常:
但是这种验证方式的复用性比较差,一个网站中提交的数据不单单只是文章的数据,还会有其他需要验证的数据,这时就要重新定义另外的验证管道进行验证,这样是不方便的
使用DTO
进行验证
DTO
是数据传输对象,对前端传递过来的数据进行处理,使用DTO
进行验证有很好的复用性
我们在src
目录中创建dto
文件夹,在文件夹中创建create.article.dto.ts
文件,用来实现对传递过来的文章数据进行验证处理:
import { IsNotEmpty, Length } from 'class-validator';
export default class CreateArticleDto {
@IsNotEmpty({ message: '标题不能为空' })
@Length(1, 10, { message: '标题长度在1到10之间' })
title: string;
@IsNotEmpty({ message: '内容不能为空' })
content: string;
}
这里使用的是类
class
,而没有使用接口的方式进行定义。因为在ts
文件最终被编译成js
的时候,ts
这些声明都是会被忽略掉的,我们这里使用类进行定义,保证被编译成js
时,这个验证过程还是有效的使用
@IsNotEmpty()
装饰器对数据进行验证,这个装饰器需要使用class-validator
包进行导入,@IsNotEmpty()
装饰器的验证规则是不能为空,字段内容的属性都会经过这个装饰器进行验证处理,如果数据,没有值,就会抛出异常,我们可以在@IsNotEmpty()
中定义错误消息:@IsNotEmpty({message:'内容不能为空'})
@Length()
装饰器是对内容长度的验证,可以将多条验证规矩进行一起使用,但都是管道最近的一个字段具体的详细规则可以去
class-validator
包的文档中进行查看
如果有其他的输入数据进行验证,我们可以创建不同的验证文件
之后,在控制器文件app.controller.ts
中进行如下的修改:
import { Body, Controller, Post } from '@nestjs/common';
import { AppService } from './app.service';
import { RequestPipe } from './request/request.pipe';
import CreateArticleDto from './dto/create.article.dto';
import { PrismaClient } from '@prisma/client';
@Controller()
export class AppController {
prisma: PrismaClient;
constructor(private readonly appService: AppService) {
this.prisma = new PrismaClient();
}
@Post('store')
async add(@Body(RequestPipe) dto: CreateArticleDto) {
// 往数据库中添加数据
await this.prisma.article.create({
data: {
title: dto.title,
content: dto.content,
thumb: "https://www.baidu.com", // 由于数据表设计,加上额外的两个字段内容
categoryId: 1,
},
});
}
}
我们要在控制器中要告知
dto
这个数据是CreateArticleDto
类型的
在管道文件request.pipe.ts
中:
import { ArgumentMetadata, HttpException, HttpStatus, Injectable, PipeTransform } from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
@Injectable()
export class RequestPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
const object = metadata.metatype ? plainToInstance(metadata.metatype, value) : value;
const errors = await validate(object);
// 深入读取错误信息,自定义错误消息内容
if (errors.length) {
const message = errors.map((error) => ({
name: error.property,
message: Object.values(error.constraints ? error.constraints : {}).map((v) => v),
}));
throw new HttpException(message, HttpStatus.BAD_REQUEST);
}
return value;
}
}
metadata
:原数据包含内容的数据类型就变成了class CreateArticleDt
{ metatype: [class CreateArticleDto], type: 'body', data: undefined }
但是
value
是没有类型的,只会显示Object
,因此,对于这个数据要按照CreateArticleDt
类去实例化出对象,我们需要使用工具plainToInstance()
,将数据value
通过metadata.metatype
构造函数,去实例化出一个对象,这时object
的类型就是类CreateArticleDt
,其内容就是value
的内容,数据会依次分配给新生成的对象我们需要使用
validate()
方法让装饰器@IsNotEmpty()
执行,validate()
返回的是Promise
,因此需要使用异步去加载
如果输入符合添加的内容,就会往数据库中进行数据的添加:
使用系统验证管道
上节是我们自己编写的管道进行验证,当用户没有按照规范提交消息的时候,会抛出异常提示,验证和抛出异常的逻辑,我们可以不用自己进行编写,系统提供了系统管道验证供我们进行直接使用,在定义完管道后,我们可以进行直接的调用
在入口文件main.ts
中进行定义:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe()); // 定义全局验证管道
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
ValidationPipe
就是一个js
的普通类,我们可以对其进行继承,继承之后加入一些自定义的内容,我们使用命令来创建类:nest g cl validate --no-spec
,创建完后,就在src/validate
目录中出现validate.ts
文件
在validate.ts
文件中,对系统提供的ValidationPipe
验证管道类进行继承:
import { ValidationError, ValidationPipe } from "@nestjs/common";
export class Validate extends ValidationPipe {
protected mapChildrenToValidationErrors(
error: ValidationError,
parentPath?: string
): ValidationError[]{
// 对错误的验证消息进行处理
const errors = super.mapChildrenToValidationErrors(error, parentPath);
errors.map(error => {
for (const key in error.constraints) {
error.constraints[key] = error.property + '-' + error.constraints[key];
}
})
return errors;
}
}
我们可以点入
ValidationPipe
类中查看该类的所有方法:tsexport declare class ValidationPipe implements PipeTransform<any> { protected isTransformEnabled: boolean; protected isDetailedOutputDisabled?: boolean; protected validatorOptions: ValidatorOptions; protected transformOptions: ClassTransformOptions | undefined; protected errorHttpStatusCode: ErrorHttpStatusCode; protected expectedType: Type<any> | undefined; protected exceptionFactory: (errors: ValidationError[]) => any; protected validateCustomDecorators: boolean; constructor(options?: ValidationPipeOptions); protected loadValidator(validatorPackage?: ValidatorPackage): ValidatorPackage; protected loadTransformer(transformerPackage?: TransformerPackage): TransformerPackage; transform(value: any, metadata: ArgumentMetadata): Promise<any>; createExceptionFactory(): (validationErrors?: ValidationError[]) => unknown; protected toValidate(metadata: ArgumentMetadata): boolean; protected transformPrimitive(value: any, metadata: ArgumentMetadata): any; protected toEmptyIfNil<T = any, R = T>(value: T, metatype: Type<unknown> | object): R | object | string; protected stripProtoKeys(value: any): void; protected isPrimitive(value: unknown): boolean; protected validate(object: object, validatorOptions?: ValidatorOptions): Promise<ValidationError[]> | ValidationError[]; protected flattenValidationErrors(validationErrors: ValidationError[]): string[]; protected mapChildrenToValidationErrors(error: ValidationError, parentPath?: string): ValidationError[]; protected prependConstraintsWithParentProp(parentPath: string, error: ValidationError): ValidationError; }
我们可以使用
mapChildrenToValidationErrors
方法来处理我们的错误消息
对于全局定义的管道,我们也不需要在根控制器文件中进行引入,会自动生效的,根控制器文件:
import { Body, Controller, Post } from '@nestjs/common';
import { AppService } from './app.service';
import CreateArticleDto from './dto/create.article.dto';
import { PrismaClient } from '@prisma/client';
@Controller()
export class AppController {
prisma: PrismaClient;
constructor(private readonly appService: AppService) {
this.prisma = new PrismaClient();
}
@Post('store')
async add(@Body() dto: CreateArticleDto) {
await this.prisma.article.create({
data: {
title: dto.title,
content: dto.content,
thumb: "https://www.baidu.com",
categoryId: 1,
},
});
}
}
当我们没有按照要求发送内容是,会显示我们自定义的错误消息内容:
使用过滤器处理验证异常
使用管道的时候,异常都会被系统捕获到,捕获到后会处理这个异常,这个异常可以通过过滤器来进行控制
创建过滤器:nest g f validate-exception --no-spec
过滤器的绑定方式和管道类似,下面展示在入口文件main.ts
中进行全局绑定:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Validate } from './validate/validate';
import { ValidateExceptionFilter } from './validate-exception/validate-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new Validate()); // 定义全局验证管道
app.useGlobalFilters(new ValidateExceptionFilter); // 定义全局过滤器
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
在过滤器文件中validate-exception.filter.ts
可以对我们的异常进行处理:
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
@Catch()
export class ValidateExceptionFilter<T> implements ExceptionFilter {
catch(exception: T, host: ArgumentsHost) {
console.log(123)
}
}
当我们发生异常的时候,就会被过滤器捕获,如果用户输入不符合规范,就会在终端中显示123
一般,我们会在过滤器中进行异常的判断,对特定的异常进行处理:
import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter, HttpStatus } from '@nestjs/common';
@Catch()
export class ValidateExceptionFilter<T> implements ExceptionFilter {
catch(exception: T, host: ArgumentsHost) {
// 接收一下异常,获取http中的上下文
const ctx = host.switchToHttp();
// 获取一下响应对象
const response = ctx.getResponse();
// 对表单验证异常进行判断,异常来源于请求错误,格式化响应错误
if (exception instanceof BadRequestException) {
const responseObject = exception.getResponse() as any;
// 返回的状态码是422
return response.status(HttpStatus.UNPROCESSABLE_ENTITY).json({
// 设置返回的状态码和消息
code: HttpStatus.UNPROCESSABLE_ENTITY,
message: responseObject.message.map((error) => {
const info = error.split('-')
return {field:info[0],message:info[1]}
})
})
}
// 如果不是表单异常,就响应以下的内容,返回的状态码是400
response.status(400).json({})
}
}
我们要保证异常有响应的内容,否则就会被阻塞
输入不规范的内容,会显示:
有了错误的消息,前端就会拿这个错误的消息进行验证
自定义验证规则
不管系统为我们提供多少的验证规则,在特殊的情况下都可能会出现不够用的情况,因此我们需要自定义验证规则来满足我们的需求
密码比对验证规则
用户在登录的时候,我们后端要对用户两次输入的密码进行比对验证,我们需要自定义密码比对验证规则
自定义类来进行验证
对于用户
User
表解构:tsmodel User { id Int @id @default(autoincrement()) @db.UnsignedInt email String @db.Char(50) password String name String? avatar String? github String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
创建一个模块和控制器:
nest g mo auth --no-spec
nest g co auth --no-spec
在控制器
auth.controller.ts
中完成注册,同时引入dto
验证:tsimport { Body, Controller, Post } from '@nestjs/common'; import RegisterDto from './dto/register.dto'; @Controller('auth') export class AuthController { @Post('register') register(@Body() dto: RegisterDto) { console.log(dto); return dto; } }
创建一个
dto
文件进行验证:在src/auth/dto/register.dto.ts
,register.dto.ts
文件的内容为:tsimport { IsNotEmpty, Length, Validate } from 'class-validator'; import { IsConfirmed } from 'src/rules/is-confirmed.rule'; export default class RegisterDto { @IsNotEmpty({ message: '用户名不能为空' }) name: string; @IsNotEmpty({ message: '密码不能为空' }) @Validate(IsConfirmed, { message: '确认密码输入错误' }) password: string; }
@IsNotEmpty
是直接使用系统提供的规则进行验证,使用装饰器进行验证@Validate
是使用类来进行验证创建一个密码确认验证规则:创建验证比对文件
src/rules/is-confirmed.rule.ts
,is-confirmed.rule.ts
的内容为:tsimport { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator"; @ValidatorConstraint() export class IsConfirmed implements ValidatorConstraintInterface { // 进行密码比对 async validate(value: string, args: ValidationArguments) { // 返回假表示验证失败 return value === args.object[args.property + '_confirmed']; } // 默认消息:比对失败的提示消息 defaultMessage(args: ValidationArguments) { return '比对失败'; } }
value
:表示传入的内容值,在dto
文件中@Validate(IsConfirmed)
下面定义的数据内容args
:表示原数据property
:表示验证哪个字段object
:表示整个表单提交的数据value
:验证表单字段的具体值
如果发送的两个密码不一致,就会显示确认密码输入错误:
如果两次密码一致,数据就会被成功发送
自定义装饰器进行验证
在之前的基础上加上一个规则:如果数据库中已经有了当前输入name
字段内容一致的数据,那么不能进行注册,使用自定义装饰器进行验证:
在
src/rules
文件夹中添加自定义装饰器is-not-exists.rule.ts
,具体内容为:tsimport { PrismaClient } from "@prisma/client"; import { registerDecorator, ValidationArguments, ValidationOptions } from "class-validator"; // 装饰器执行的函数,函数可以进行参数的设置,从外界中传入参数 export function IsNotExistsRule( // table表示定义具体操作数据中的哪张表 table: string, validationOptions?: ValidationOptions, ) { return function (object: Record<string, any>, propertyName: string) { registerDecorator({ name: "IsNotExistsRule", target: object.constructor, propertyName: propertyName, constraints: [table], options: validationOptions, validator: { async validate(value: string, args: ValidationArguments) { const prisma = new PrismaClient(); const user = await prisma[table].findFirst({ where: { // propertyName变量的属性名是name [propertyName]: args.value, }, }); // 返回假表示验证失败 return !Boolean(user); }, }, }) } }
参数:
value
和args
一个表示具体值,一个表示原数据,和类中返回的参数差不多在
dto
文件中直接使用自定义的装饰器:tsimport { IsNotEmpty, Length, Validate } from 'class-validator'; import { IsConfirmed } from 'src/rules/is-confirmed.rule'; import { IsNotExistsRule } from 'src/rules/is-not-exists.rule'; export default class RegisterDto { @IsNotEmpty({ message: '用户名不能为空' }) @IsNotExistsRule('user', { message: '用户已经存在' }) // 第一个参数表示验证哪张表 name: string; @IsNotEmpty({ message: '密码不能为空' }) @Validate(IsConfirmed, { message: '确认密码输入错误' }) password: string; }
如果输入的name
的内容在数据库中已经存在,就会提示用户已经存在: