全棧項目|小書架|服務器開發-Koa2 全局異常處理

什麼是異常

作開發的基本都知道異常,像Android開發中常見的ANR異常、空指針異常,服務器開發中常常遇到的異常404,500異常,還有一些其餘常見的異常,具體可見HTTP狀態碼數據庫

基本上這些異常能夠總結爲:已知異常未知異常api

已知異常就是程序中可以預想到異常,好比:服務器接口開發中某個api接口須要5個參數,而用戶傳遞的參數多餘5個或者少於5個,這種錯誤就是已知錯誤。服務器

未知異常就說程序中不能預想到的異常,好比:服務器接口開發中遇到了空指針而程序中又沒有作相應處理就會拋出HTTP狀態碼爲500的這種異常,這種就說未知異常。微信

爲何須要全局異常處理

當遇到異常時若是沒有全局異常處理,通常是在相應的代碼邏輯中添加異常捕捉(try ... catch)或者拋出(throw)處理。網絡

這麼作實際上是有弊端的:app

  1. 程序代碼判斷邏輯過長,可讀性查,不方便後期維護。
  2. 代碼耦合性高,每次出現異常都須要在不一樣的類、文件下寫異常判斷邏輯。

以上只是列舉的幾個弊端,爲了解決以上的問題程序中添加全局異常的處理就頗有必要了。async

這裏使用的是NodeJS+Koa2開發。在Koa中,中間件是無處不在,因此這裏全局異常的處理也是經過中間件的方式去實現。工具

如何處理

一、明確是否須要拋出異常開發工具

在服務器接口開發中須要明確是生產環境仍是開發環境ui

生產環境中若是出現異常須要將詳細的異常信息上報同時將異常狀態經過api返回給客戶端處理

開發環境中若是出現異常則須要將詳細的異常信息在開發工具的控制檯顯示,同時返回將異常狀態經過api返回給客戶端處理。

這裏的區別就說生產環境開發環境,因此經過定義一個全局變量去判斷便可。因爲程序中全局變量可能不止一個,爲了統一聲明全局變量,咱們將全部的全局變量放在一個文件中,統一去加載。

新建一個config.js,裏面的environment:'dev'就是對環境聲明的變量,這裏dev表示開發環境prod表示生產環境

module.exports = {
    // prod 表示生產環境
    environment:'dev',
    database:{
        dbName:'book',
        host:'localhost',
        port:3306,
        user:'用戶名',
        password:'密碼',
    },
    // token 設置爲1天
    security:{
        secretKey:"密鑰,要記住不能弄丟了哦",
        expiresIn:60*60*24
    },
    host:'http://localhost:3000/'
}

配置了全局變量以後,在init.js文件的InitManager類中定義靜態方法:

/**
 * 加載全局配置文件
 * @param {''} path 當前路徑
 */
static loadConfig(path = '') {
    const configPath = path || process.cwd() + '/config/config.js'
    const config = require(configPath)
    global.config = config
}
/**
 * 加載全局異常
 */
static loadHttpException(){
    const errors = require('./http-exception')
    global.errs = errors
}

最後在app.js中完成初始化。

const app = new Koa()
InitManager.loadConfig()
InitManager.loadHttpException()

定義全局變量以後就須要制定返回的api異常描述

二、定義異常的返回結果

在服務器接口開發中,一個異常的返回結果,一般包含有:

  • msg:異常描述
  • errorcode:自定義的異常狀態碼
  • code:網絡請求的狀態碼

兩個code的區別:

  • errorcode :自定義的錯誤碼,配合code定位具體的異常。
  • code:網絡請求的狀態碼,如403 權限受限,而權限受限的緣由有不少種,好比未登陸或者登陸了可是權限不足,這時候能夠結合自定義的錯誤碼和異常描述準確明確告知用戶出錯的緣由。

定義異常描述以後就須要去判斷程序是已知異常仍是未知異常。

三、明確是已知異常仍是未知異常

已知異常:能夠定義HttpException繼承Error這個類,只要是出現這異常屬於HttpException都屬於已知異常。

http-exception.js

/**
 * 默認的異常
 */
class HttpException extends Error{
    constructor(msg='服務器異常',errorCode=10000, code=400){
        super()
        this.errorCode = errorCode
        this.code = code
        this.msg = msg
    }
}

/**
 * 資源未找到提示
 */
class NotFound extends HttpException{
    constructor(msg, errorCode) {
        super()
        this.msg = msg || '資源未找到'
        this.errorCode = errorCode || 10000
        this.code = 404
    }
}

module.exports = {
    HttpException,
    NotFound
}

環境變量聲明瞭、異常也作了處理,那麼如何監聽全局異常呢?

四、全局異常監聽

首先編寫捕捉異常處理中間件catchError.js

const {HttpException} = require('../core/http-exception')

const catchError = async (ctx, next)=>{
    try {
        await next()
    } catch (error) {
        // 已知異常
        const isHttpException = error instanceof HttpException
        // 開發環境
        const isDev = global.config.environment === 'dev'
         // 在控制檯顯示未知異常信息:開發環境 不是HttpException 拋出異常
        if(isDev && !isHttpException){
            throw error
        }
        
        /**
         * 是已知錯誤,仍是未知錯誤
         * 返回:
         *      msg 錯誤信息
         *      error_code 錯誤碼
         *      request 請求的接口路徑
         */
        if(isHttpException){
            ctx.body = {
                msg:error.msg,
                error_code:error.errorCode,
                request:`${ctx.method} ${ctx.path}`
            }
            ctx.status = error.code
        }
        else{
            ctx.body = {
                msg: '服務器出現了未知異常',
                error_code: 999,
                request:`${ctx.method} ${ctx.path}`
            }
            ctx.status = 500
        }
    }
}

module.exports = catchError

而後在app.js中加載中間件

const catchError = require('./middlewares/exception')
const app = new Koa()
// 全局異常中間件監聽、處理,放在全部中間件的最前面
app.use(catchError)
app.listen(3000)

以上就完成了全局異常的處理,接下來就是如何使用這個全局異常了。

五、出現異常後及時拋出異常

這裏以資源未找到爲例。在查詢數據庫中,有的時候會出現數據找不到狀況,這是用就能夠拋出資源未找到的異常。

models/book.js

/**
  * 獲取書籍詳情
  * @param {書籍id} bid 
  */
 static async detail(bkid) {
     const book =await Book.findOne({
         where: {
             bkid
         }
     })
     if (!book) {
        // 經過全局異常的方式,拋出資源未找到的錯誤
         throw new global.errs.NotFound()
     }
     return book
 }

諮詢請加微信:輕撩便可。
在這裏插入圖片描述

相關文章
相關標籤/搜索