Nodejs Nestjs 路程 之 異常過濾器Exceptionfilter

baidixing發表於2021-02-06

參考文件: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的異常處理並不複雜,複雜的是需要我們針對不同型別的異常進行處理,提取異常的共性。

相關文章