当前位置 博文首页 > baidixing:Nodejs Nestjs 路程 之 异常过滤器Exceptionfilter

    baidixing:Nodejs Nestjs 路程 之 异常过滤器Exceptionfilter

    作者:baidixing 时间:2021-02-06 20:24

    Nestjs

    参考文档:docs.nestjs.cn

    说起Nestjs的异常过滤器,不能不提.Net的全局过滤器Filter,功能那是相当的强悍,用理论话说叫AOP 面向切面编程,可谓方便了太多需要异常处理的场景。说回Nestjs的异常过滤器,实现类似的功能,采用相似的处理方式,只不过一个面向C#,一个面向Nodejs,很荣幸的我,在两个框架都找到了类似的东西。

    面向切面编程AOP,是一种类似于编程规范的东东,同门师兄弟有叫面向接口编程、SOLID原则等等。

    Nestjs的异常处理

    默认异常处理

    Nestjs内置了默认的全局异常过滤器,处理能够转换成Httpexception的异常。

    如果是Httpexception或其子类异常,那么会返回该异常的JSON格式:

    {"exceptionCode":40005,"message":"自定义异常","path":"/"}

    如果不是Httpexception或其子类异常,那么会返回:

    {"statusCode":500,"message":"Internal server error"}

    由于Nestjs采用了内置的默认异常处理,因此不会出现由于出现未捕获的异常导致程序崩溃。

     

    自定义异常过滤器处理

    由于内置异常处理返回值格式无法调整,因此自定义异常就显得又为正常。自定义异常可以使返回异常信息自定义,且可以增加自定义异常编码,方便客户端人员根据异常编码进行不同的展示。

    如何自定义异常?

    不重复造轮子是程序员的自我约束,首先我们新建我们自己的异常基类:

     1 import { HttpException } from "@nestjs/common";
     2 
     3 /**
     4  * 定义基础异常类
     5  *
     6  * @export
     7  * @class BaseException
     8  * @extends {HttpException}
     9  */
    10 export class BaseException extends HttpException {
    11 
    12     /**
    13      * Creates an instance of BaseException.
    14      * @param {number} exceptionCode 自定义异常编号
    15      * @param {string} errorMessage  提示信息
    16      * @param {number} statusCode 状态码
    17      * @memberof BaseException
    18      */
    19     constructor(public exceptionCode: number, public errorMessage: string, public statusCode: number) {
    20         super({ exceptionCode: exceptionCode, errorMessage: errorMessage }, statusCode);        
    21     }
    22 
    23     /**
    24      * 获取自定义异常代码
    25      *
    26      * @return {*} 
    27      * @memberof BaseException
    28      */
    29     getExceptionCode(): number {
    30         return this.exceptionCode;
    31     }
    32 
    33     getErrorMessage(): string {
    34         return this.errorMessage;
    35     }
    36 
    37     getStatusCode(): number {
    38         return this.statusCode;
    39     }
    40 }

    然后我们新建一个未授权异常类型,其中增加了自定义异常代码:

    1 import { HttpStatus } from "@nestjs/common";
    2 import { BaseException } from "./base.exception";
    3 
    4 export class UnCauhtException extends BaseException {
    5     constructor() {
    6         super(40000, "系统运行异常,请联系管理员!", HttpStatus.FORBIDDEN);
    7     }
    8 }

    建立好了自定义异常,那么我们就需要处理未授权异常,首先新建自定义异常处理基类,请注意 此处我们使用的事Express

     1 import { ArgumentsHost, ExceptionFilter, HttpException } from "@nestjs/common";
     2 import { HttpArgumentsHost } from "@nestjs/common/interfaces";
     3 import { BaseException } from "src/exceptions/base.exception";
     4 import { Response, Request } from "express";
     5 
     6 /**
     7  * 异常基础类过滤器
     8  *
     9  * @export
    10  * @class BaseExceptionFilter
    11  * @implements {ExceptionFilter<BaseException>}
    12  */
    13 export abstract class BaseExceptionFilter implements ExceptionFilter<BaseException>
    14 {
    15     /**
    16      * 异常类捕获
    17      *
    18      * @abstract
    19      * @param {BaseException} exception
    20      * @param {ArgumentsHost} host
    21      * @memberof BaseExceptionFilter
    22      */
    23     abstract catch(exception: BaseException, host: ArgumentsHost);
    24 
    25     /**
    26      * 获取http请求上下文参数
    27      *
    28      * @protected
    29      * @param {ArgumentsHost} host
    30      * @return {*} 
    31      * @memberof BaseExceptionFilter
    32      */
    33     protected getHttpContext(host: ArgumentsHost) {
    34         return host.switchToHttp();
    35     }
    36 
    37     /**
    38      * 获取http 响应参数
    39      *
    40      * @protected
    41      * @param {HttpArgumentsHost} httpContext
    42      * @return {*} 
    43      * @memberof BaseExceptionFilter
    44      */
    45     protected getResponse(httpContext: HttpArgumentsHost): Response {
    46         return httpContext.getResponse<Response>();
    47     }
    48 
    49     /**
    50      * 获取http请求参数
    51      *
    52      * @protected
    53      * @param {HttpArgumentsHost} httpContext
    54      * @return {*} 
    55      * @memberof BaseExceptionFilter
    56      */
    57     protected getRequest(httpContext: HttpArgumentsHost): Request {
    58         return httpContext.getRequest<Request>();
    59     }
    60 
    61     /**
    62      * 写入异常信息到客户端
    63      *
    64      * @param {ArgumentsHost} host
    65      * @param {BaseException} exception
    66      * @memberof BaseExceptionFilter
    67      */
    68     protected writeToClient(host: ArgumentsHost, exception: BaseException) {
    69         const ctx = this.getHttpContext(host);
    70         if(exception instanceof BaseException){            
    71             this.getResponse(ctx).status(exception.statusCode).json({
    72                 exceptionCode: exception.getExceptionCode(),
    73                 message: exception.getErrorMessage(),
    74                 path: this.getRequest(ctx).url
    75             });
    76         }else {
    77             const httpException=exception ;
    78             this.getResponse(ctx).status(500).json({                
    79                 message: "未处理的异常",
    80                 path: this.getRequest(ctx).url
    81             });
    82         }
    83         
    84     }
    85 }

    新建未授权异常处理:

     1 import { ArgumentsHost, Catch } from "@nestjs/common";
     2 import { AuthException } from "src/exceptions/auth.exception";
     3 import { BaseException } from "src/exceptions/base.exception";
     4 import { BaseExceptionFilter } from "./base.exception.filter";
     5 
     6 @Catch(AuthException)
     7 export class AuthExceptionFilter extends BaseExceptionFilter
     8 {
     9     constructor(){
    10         super();
    11         console.log("授权异常构造函数初始化"+new Date().toISOString());
    12     }
    13     catch(exception: AuthException, host: ArgumentsHost) {
    14         exception.exceptionCode=40002;
    15         console.log("授权异常执行"+new Date().toISOString());
    16         this.writeToClient(host,exception);
    17     }
    18     
    19 }

    针对未授权异常处理类,进行几点说明:

    1. 增加了Catch注解,只捕获Authexception的异常,其他类型的异常此类不进行处理
    2. 继承自定义异常处理类Baseexceptionfilter

    应用范围

    异常处理类可应用于method、controller、全局,甚至同一个Controller可以定义多个自定义异常类

     1 import { Controller, ForbiddenException, Get, HttpException, HttpStatus, UseFilters } from '@nestjs/common';
     2 import { AppService } from './app.service';
     3 import { AuthException } from './exceptions/auth.exception';
     4 import { BusinessException } from './exceptions/business.exception';
     5 import { UnCauhtException } from './exceptions/uncauht.exception';
     6 import { AuthExceptionFilter } from './filters/auth.exception.filter';
     7 import { BusinessExceptionFilter } from './filters/business.exception.filter';
     8 
     9 
    10 /**
    11  * 带有单个路由的基本控制器示例ff
    12  */
    13 @UseFilters(AuthExceptionFilter,BusinessExceptionFilter)
    14 @Controller()
    15 export class AppController {
    16   constructor(private readonly appService: AppService) {}
    17 
    18   @Get()
    19   getHello(): string {   
    20     //throw new Error("666");
    21     throw new BusinessException("自定义异常",HttpStatus.OK);
    22     throw new AuthException();
    23     throw new HttpException("自定义异常",HttpStatus.FORBIDDEN);
    24     return this.appService.getHello();
    25   }
    26 
    27   @Get("name")
    28   getName():string
    29   {
    30     return "guozhiqi";
    31   }
    32 }

    几点说明:

    1. 我们使用Usefilters注解进行异常过滤器的添加
    2. 我们在Appcontroller中定义了两种不同类型的自定义异常处理类
    3. 也就是我们Appcontroller中抛出的异常,只要是我们定义的这两种,那么都可以被正常处理。

    几点疑问

    1. Usefitlers中我们自定义的异常处理类会初始化几次?
      答案:我们通过类型注册到Appcontroller的自定义异常类只会在程序初始化的时候初始化一次。也就是说程序启动之后,每个controller、每个method定义了哪些异常处理类都已经确定。
    2. 如果我们捕获到异常,但不进行任何处理,会发生什么?
      答案:如果我们的异常处理方法什么也不做,那么恭喜你,会成功的将浏览器请求hang死,因为异常未处理,那么浏览器会一直得不到响应。
    3. 多个异常之间的处理顺序如何?
      答案:如果多个异常处理均可以捕获到该异常,那么只有第一个有效,也就是说异常处理类和 中间件不同,异常处理类只能其中一个处理,而中间件需要都进行处理。
    4. Nestjs的@Usefilters 像谁?
      首先从JS角度来看,像Angular,如果往后端看,最像Spring。

     

    Nestjs的异常处理并不复杂,复杂的是需要我们针对不同类型的异常进行处理,提取异常的共性。

    bk