本文转载请联系神光编程秘籍公众号。验证输入是一个web应用的基本功能,不仅是前端,后端也是如此:前端验证可以避免不必要的请求,尽快反馈给用户后端验证可以防止一些恶意浏览器绕过前端提交表单的验证基本不用写。验证库很多,大家都写了很多。您可能会编写相对较少的后端验证。今天我们就来学习一下后端框架Nest.js是如何进行参数校验的。本文将学习这些东西:Nest.js管道(pipe)用于参数验证和转换Nest.js异常过滤器(exceptionfilter)用于异常处理,返回响应Nest.js结合类验证用于声明式参数验证Nest.js基础Nest.js是基于IOC和MVC思想的后端框架:MVC是Controller、Service和Repository的分层,也是后端框架的通用架构。IOC即依赖注入,即可以在IOC容器中自动注入Controller和Service、Repository等实例,只需要声明依赖,无需手动new。另外,Nest.js还支持Module,可以将Controller、Service、Repository封装成一个Module,便于组织代码。整体架构如图:整个IOC容器中有多个Controller、Service、Respository等实例,分散在不同的Module中。有一个AppModule作为导入其他模块的根。请求在Controller中处理,调用Service完成业务逻辑。对数据库的CRUD是由Repository完成的。那么参数的validate应该放在哪里呢?parametervalidate的实现思路是对参数进行验证,可以在Controller中完成,但是这种验证逻辑是通用的,在每个Controller中都做太麻烦。可以在Controller中完成吗?控制器以前做过吗?可能你不知道,那我们来了解下Nest.js的另一个功能:管道。Nest.js支持管道(Pipe),在请求到达Controller之前会被调用,可以对参数进行校验和转换。如果抛出异常,则不会将其传递给Controller。这种pipeline的特点适合一些跨Controller的通用逻辑,比如string转int,参数校验等。Nest.js内置了8个管道:ValidationPipeParseIntPipeParseBoolPipeParseArrayPipeParseUUIDPipeParseEnumPipeParseFloatPipeDefaultValuePipe可以分为3类:parseXxx,将参数转换为某种类型;defaultValue,设置参数的默认值;验证,验证参数。这些是非常通用的功能。显然,可以使用该ValidationPipe来完成验证。不过先不急着用Nest.js提供的Pipe,还是先自己实现吧。Pipe的形式是一个实现了PipeTransform接口的类,实现了它的transform方法,对里面的值进行各种变换或者校验,校验失败则抛出异常。import{PipeTransform,Injectable,ArgumentMetadata,BadRequestException}from'@nestjs/common';@Injectable()exportclassMyValidationPipeiimplementsPipeTransform{asynctransform(value:any,metadata:ArgumentMetadata){if(value.age>20){thrownewBadRequestException('Ageexceedsthelimit');}else{value.age+=10;}returnvalue;}}之后,我们在IOC容器启动时调用useGlobalPipes方法注册这个Pipe:import{NestFactory}from'@nestjs/core';import{AppModule}from'./app.module';import{MyValidationPipe}from'./pipes/MyValidationPipe';asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);app.useGlobalPipes(newMyValidationPipe());awaitapp.listen(3000);}bootstrap();我们来测试一下:当参数age大于20时,会抛出异常,并返回相应的response。当参数小于20时,参数将被修改并传递给Controller:可以看到,参数已经传递给Controller并被修改了。这就是Pipes的用途。因此,我们可以验证管道中的参数。你可以使用类验证包,它支持装饰器来配置验证规则:像这样:empty'})@IsString()name:string;@IsPhoneNumber("CN",{message:'phone不是电话号码'})phone:string;@IsEmail({},{message:'email不是legalEmail'})email:string;}然后调用pipe中的validate方法,如果出错则抛出异常:import{PipeTransform,Injectable,ArgumentMetadata,BadRequestException}from'@nestjs/common';import{validate}from'class-validator';import{plainToClass}from'class-transformer';@Injectable()exportclassMyValidationPipeimplementsPipeTransform{asynctransform(value:any,{metatype}:ArgumentMetadata){if(!metatype){returnvalue;}constobject=plainToClass(metatype,value);consterrors=awaitvalidate(object);if(errors.length>0){thrownewBadRequestException('Validationfailed');}returnvalue;}}因为如果我们使用装饰器来做配置,需要通过object获取它对应类的装饰器,所以之前应该调用class-transformer包的plainToClass方法将普通参数对象转换为这个类证实这样就实现了参数校验的功能:这就是Nest.js的ValidationPipe的实现原理。当然,我们没有做错格式,不如内置的Pipe漂亮。我们来看看内置Pipe的效果:启用内置ValidationPipe:import{ValidationPipe}from'@nestjs/common';import{NestFactory}from'@nestjs/core';import{AppModule}from'./app.module';asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);app.useGlobalPipes(newValidationPipe());awaitapp.listen(3000);}bootstrap();然后测试:别人返回的格式好很多。还有,你有没有注意到,我们刚刚返回了一个BadRequestException错误,但是服务器返回了一个400响应。是什么原因?这就涉及到Nest.js的另一个机制:异常过滤器(ExceptionFilter)。Nest.js支持异常过滤器(ExceptionFilter),它可以声明对什么错误做出什么响应,这样应用程序只需要抛出相应的异常就可以返回任何响应。异常过滤器的形式是一个实现了ExceptionFilter接口的类,通过Catch装饰器声明处理什么异常。实现它的catch方法,在方法中获取response对象并返回相应的response。定义异常:exportclassForbiddenExceptionextendsHttpException{constructor(){super('Forbidden',HttpStatus.FORBIDDEN);}}定义异常过滤器:import{ExceptionFilter,Catch,ArgumentsHost,HttpException}from'@nestjs/common';import{Request,响应}from'express';@Catch(HttpException)exportclassHttpExceptionFilterimplementsExceptionFilter{catch(exception:HttpException,host:ArgumentsHost){constctx=host.switchToHttp();constresponse=ctx.getResponse();constrequest=st();conststatus=exception.getStatus();response.status(status).json({statusCode:status,timestamp:newDate().toISOString(),path:request.url,});}}显然,我们之所以只是在ValidationPipe中抛出BadRequestException错误并返回400响应,是因为内置的ExceptionFilter。Nest.js有很多内置的ExceptionFilters,比如:BadRequestException返回400,表示客户端传递的参数错误;ForbiddenException返回403,表示没有权限;ExceptionFilter可以自定义。至此,我们通过Pipe+ExceptionFilter实现了参数的校验。总结一下,输入的校验是一个基础功能,前后端都要做。先来了解一下Nest.js的基础知识:Nest.js是MVC+IOC架构,支持Module组织代码。然后探究一下Nest.js中validate的实现思路:validation可以放在Controller之前,通过Pipe进行参数的校验和转换。如果有错误,就会抛出异常,异常会触发ExceptionFilter,从而返回不同的错误响应。Pipe在Controller之前被调用,如果抛出异常,请求将不会传递给Controller。ExceptionFilter可以监控不同类型的异常,做出不同的响应。有很多内置的Pipes和ExceptionFilters可以直接使用,不够用的时候可以自己定义。当然,如果只是实现校验,就不用那么麻烦了,直接使用ValidationPipe即可。Validation是一个基础功能,但是我们通过它学习了Pipe和ExceptionFilter,还是很有意义的。