參考文件: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 }
針對未授權異常處理類,進行幾點說明:
- 增加了Catch註解,只捕獲Authexception的異常,其他型別的異常此類不進行處理
- 繼承自定義異常處理類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 }
幾點說明:
- 我們使用Usefilters註解進行異常過濾器的新增
- 我們在Appcontroller中定義了兩種不同型別的自定義異常處理類
- 也就是我們Appcontroller中丟擲的異常,只要是我們定義的這兩種,那麼都可以被正常處理。
幾點疑問
- Usefitlers中我們自定義的異常處理類會初始化幾次?
答案:我們通過型別註冊到Appcontroller的自定義異常類只會在程式初始化的時候初始化一次。也就是說程式啟動之後,每個controller、每個method定義了哪些異常處理類都已經確定。 - 如果我們捕獲到異常,但不進行任何處理,會發生什麼?
答案:如果我們的異常處理方法什麼也不做,那麼恭喜你,會成功的將瀏覽器請求hang死,因為異常未處理,那麼瀏覽器會一直得不到響應。 - 多個異常之間的處理順序如何?
答案:如果多個異常處理均可以捕獲到該異常,那麼只有第一個有效,也就是說異常處理類和 中介軟體不同,異常處理類只能其中一個處理,而中介軟體需要都進行處理。 - Nestjs的@Usefilters 像誰?
首先從JS角度來看,像Angular,如果往後端看,最像Spring。
Nestjs的異常處理並不複雜,複雜的是需要我們針對不同型別的異常進行處理,提取異常的共性。