当前位置: 首页 > 后端技术 > Node.js

NestJs学习之旅(六)——异常处理

时间:2023-04-03 20:36:51 Node.js

欢迎继续关注NestJs学习之旅系列文章,关注公众号获取最新教程!传统的异常处理在前面的内容中,我们介绍了NestJs常用的几个组件,但是有一点没有说明。当我们的应用程序需要中断请求并输出错误信息时,我们需要做什么呢?这个问题有两种解决方法:services层直接返回中断请求的响应对象,controller可以直接输出该对象if(!this.allowLogin()){return{errcode:403,errmsg:'Loginnot允许'};}服务层抛出异常,控制器捕获异常,然后输出响应对象。以上两种方式都有一定的缺点:控制器调用多个服务时,需要根据服务层的返回值进行错误判断。如果判断失误,将导致原本需要中断的请求处理继续运行,造成不可预知的后果。如果每个controller都需要try/catchservices层抛出的异常,就会有很多“重复”的代码。有没有类似SpringBoot的ExceptionHandler?类似的解决方案呢?NestJs异常处理NestJs提供了统一的异常处理程序来集中处理运行过程中未捕获的异常,并且可以自定义响应参数,非常灵活。默认响应NestJs内置了一个默认的全局异常过滤器,用于处理HttpException(及其子类)的异常。如果抛出的异常不是上述异常,将响应如下默认JSON:{"statusCode":500,"message":"Intervalservererror"}内置异常过滤器由于NestJs有内置的默认异常过滤器,如果里面抛出的应用程序HttpException可以被NestJs自动捕获。例如在services层抛出HttpException:@Injectable()exportclassUserService{login(username:string,password:string){if(!this.allowLogin()){thrownewHttpException('Youarenotauthorized登录',HttpStatus.FORBIDDEN);}return{user_id:1,token:'faketoken'}}}控制器可以正常调用服务:@Controller('users')exportclassUserController{constructor(privatereadonlyuserService:UserService){}@Post('login')login(@Body('username')username:string,@Body('password')password:string){returnthis.userService.login(username,password);}}客户端访问/user/login时,如果不允许登录,会收到如下响应:{"statusCode":403,"message":"Youarenotauthorizedtologin"}一般情况下,信息上面的json返回的还不够,比如有些业务没有地方自定义自定义错误码。如果你有这种需求,可以将对象传递给HttpException的第一个参数:thrownewHttpException({errcode:40010,errmsg:'Youarenotauthorizedtologin'},HttpStatus.FORBIDDEN);客户端访问时,如果不允许登录,会收到如下响应:{"errcode":40010,"errmsg":"Youarenotauthorizedtologin"}企业级应用开发过程中的自定义异常,使用HttpException进行处理对开发不友好。一种常用的方法是自定义一个UserException来携带业务异常(系统运行正常,但是当前请求不满足业务要求而中断。比如注册时用户名重复,回调,并且此时数据库查询正常,没错,这就是业务异常和系统异常的区别)。exportclassUserExceptionextendsHttpException{constructor(errcode:number,errmsg:string,statusCode:number){super({errcode,errmsg},statusCode);}}业务层在使用该异常时可以直接使用如下代码,将原来传递对象的代码扁平化:thrownewUserException(40010,'Youarenotauthorizedtologin',HttpStatus.FORBIDDEN);语义业务异常使用自定义异常时,HTTP协议层正常,抛出403错误有点不符合语义需求。修改上面的例子:exportclassUserExceptionextendsHttpException{constructor(errcode:number,errmsg:string){super({errcode,errmsg},HttpStatus.OK);}}thrownewUserException(40010,'您无权登录');此时客户端收到的HttpStatus为200,表示协议层请求成功,但业务层返回错误。前端在处理response时,可以直接检查errcode是否为0来判断请求是否成功。自定义异常过滤器虽然内置的异常过滤器可以自动处理很多情况,但是它不是“可编程的”,这意味着我们无法完全控制异常处理过程。如果我们需要记录日志,我们可以使用内置的异常过滤器来完成。不,这时候你可以使用@Catch注解来自定义异常处理器,添加日志记录什么的。import{ExceptionFilter,Catch,ArgumentsHost,HttpException}from'@nestjs/common';import{Request,Response}from'express';@Catch(HttpException)exportclassHttpExceptionFilterimplementsExceptionFilter{catch(exception:HttpException):ArgumentsHost){constctx=host.switchToHttp();constresponse=ctx.getResponse();constrequest=ctx.getRequest();conststatus=exception.getStatus();//@todo日志记录console.log('%s%serror:%s',request.method,request.url,exception.message);//发送响应response.status(status).json({statusCode:status,message:exception.messagepath:request.url,});}}ArgumentHostArgumentHost是原始请求的包装器。由于NestJs支持HTTP/GRPC/WebSocket,这三种请求的原始请求对象是不同的,所以可以统一异常过滤器来处理这三种异常,NestJs对它们进行了封装。最终由开发人员来处理要使用的异常。ArgumentHost接口定义如下:exportinterfaceArgumentsHost{getArgs=any[]>():T;getArgByIndex(index:number):T;switchToRpc():RpcArgumentsHost;switchToHttp():HttpArgumentsHost;switchToWs():WsArgumentsHost;}如果需要处理WebSocket异常,就用host.switchToWs(),其他的异常类推。使用自定义的异常过滤器如果你定义了一个自定义的异常过滤器,直接访问抛出异常的接口,你会发现并没有使用自定义的异常过滤器。因为我们只是定义,而不是注册。使用@UseFilters注册自定义异常过滤器。异常过滤器有以下三个作用域:方法级控制器级全局级方法级只会处理本方法抛出的异常,其他方法抛出的异常将不会处理。@Post('login')@UseFilters(UserExceptionFilter)login(@Body('username')username:string,password:string){thrownewUserException(40010,'Youarenotauthorizedtologin');}控制器级别只会处理本控制器方法抛出的异常,其他控制器抛出的异常不予处理。@Controller('user')@UseFilters(UserExceptionFilter)exportclassUserController{}全局级别注册在应用入口,不会对Websocket或混合应用生效(两种应用同时支持,如HTTP/GRPC或HTTP/WebSocket)。一般的web开发,全局异常过滤器就够了。在main.ts中注册全局异常过滤器asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);app.useGlobalFilters(newUserExceptionFilter());等待app.listen(3000);引导程序();依赖注入由于异常过滤器不属于任何模块上下文,NestJs无法对其进行依赖注入管理。如果有这样的需求,比如向异常过滤器注入服务,则需要定义一个服务提供者。服务提供者名称是NestJsimport{Module}from'@nestjs/common'指定的常量APP_FILTER;从'@nestjs/core'导入{APP_FILTER};@Module({providers:[{provide:APP_FILTER,useClass:UserExceptionFilter,},],})exportclassAppModule{}捕获多个异常或所有异常。上面示例中提到的自定义异常处理程序只会捕获UserException异常。如果出现系统异常,将使用内置的异常处理程序。通过将异常类型传递给@Catch装饰器来捕获各种异常。如果不传递任何异常类型,NestJs将捕获所有异常(即Error及其子类)。import{ExceptionFilter,Catch,ArgumentsHost,HttpException}from'@nestjs/common';import{Request,Response}from'express';@Catch()//捕获所有异常exportclassHttpExceptionFilterimplementsExceptionFilter{catch(exception:错误,主机:ArgumentsHost){constctx=host.switchToHttp();constresponse=ctx.getResponse();constrequest=ctx.getRequest();conststatus=exception.getStatus();//@todo记录日志console.log('%s%serror:%s',request.method,request.url,exception.message);//发送响应response.status(status).json({statusCode:status,message:exception.messagepath:request.url,});}}最后的异常过滤器让应用异常有一个统一的处理通道,也解决了文章开头提出的两个问题。通过自定义异常过滤器,开发者可以进行统一响应格式、统一日志记录等操作。如果觉得自己有所收获,请分享给更多有需要的朋友,谢谢!如果你想交流更多关于NestJs的知识,欢迎加入群讨论!