亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

go語言規范RESTful?API業務錯誤處理的方法是什么

發布時間:2023-03-08 10:55:46 來源:億速云 閱讀:124 作者:iii 欄目:開發技術

這篇“go語言規范RESTful API業務錯誤處理的方法是什么”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“go語言規范RESTful API業務錯誤處理的方法是什么”文章吧。

    錯誤碼

    現如今,主流的 Web API 都采用 RESTful 設計風格,對于接口返回的 HTTP 狀態碼和響應內容都有統一的規范。針對接口錯誤響應,一般都會返回一個 Code(錯誤碼)和 Message(錯誤消息內容),通常錯誤碼 Code 用來定位一個唯一的錯誤,錯誤消息 Message 用來展示錯誤信息。

    為什么需要業務錯誤碼

    雖然 RESTful API 能夠通過 HTTP 狀態碼來標記一個請求的成功或失敗,但 HTTP 狀態碼作為一個通用的標準,并不能很好的表達業務錯誤。

    比如一個 500 的錯誤響應,可能是由后端數據庫連接異常引起的、也可能由內部代碼邏輯錯誤引起,這些都無法通過 HTTP 狀態碼感知到,如果程序出現錯誤,不方便開發人員 Debug。

    因此我們有必要設計一套用來標識業務錯誤的錯誤碼,這有別于 HTTP 狀態碼,是跟系統具體業務息息相關的。

    錯誤碼功能

    在設計錯誤碼之前,我們需要明確下錯誤碼應該具備哪些屬性,以滿足業務需要。

    • 錯誤碼必須是唯一的。只有錯誤碼是唯一的才方便在程序出錯時快速定位問題,不然程序出錯,返回錯誤碼不唯一,想要根據錯誤碼排查問題,就要針對這一錯誤碼所表示的錯誤列表進行逐一排查。

    • 錯誤碼需要是可閱讀的。意思是說,通過錯誤碼,我們就能快速定位到是系統的哪個組件出現了錯誤,并且知道錯誤的類型,不然也談不上叫「業務錯誤碼」了。一個清晰可讀的錯誤碼在微服務系統中定位問題尤其有效。

    • 通過錯誤碼能夠方便知道 HTTP 狀態碼。這一點往往容易被人忽略,不過我比較推薦這種做法,因為在 Review 代碼時,通過返回錯誤碼,就能很容易知道接口返回 HTTP 狀態碼,這不僅方便理解代碼,更方便錯誤的統一處理。

    錯誤碼設計

    錯誤碼調研

    錯誤碼的設計我們可以參考業內使用量比較大的開放 API 設計,比較有代表性的是阿里云和新浪網的開放 API。

    如以下是一個阿里云 ECS 接口錯誤的返回:

    {
    	"RequestId": "5E571499-13C5-55E3-9EA6-DEFA0DBC85E4",
    	"HostId": "ecs-cn-hangzhou.aliyuncs.com",
    	"Code": "InvalidOperation.NotSupportedEndpoint",
    	"Message": "The specified endpoint can't operate this region. Please use API DescribeRegions to get the appropriate endpoint, or upgrade your SDK to latest version.",
    	"Recommend": "https://next.api.aliyun.com/troubleshoot?q=InvalidOperation.NotSupportedEndpoint&product=Ecs"
    }

    可以發現,Code 和 Message 都為字符串類型,并且還有 RequestId(當前請求唯一標識)、HostId(Host 唯一標識)、Recommend(錯誤診斷地址),可以說這個錯誤信息非常全面了。

    再來看下新浪網開放 API 錯誤返回結果的設計:

    {
    	"request": "/statuses/home_timeline.json",
    	"error_code": "20502",
    	"error": "Need you follow uid."
    }

    相比阿里云,新浪網的錯誤返回更簡潔一些。其中 request 為請求路徑,error_code 即為錯誤碼 Code,error 則表示錯誤信息 Message。

    錯誤代碼 20502 說明如下:

    20502
    服務級錯誤(1為系統級錯誤)服務模塊代碼具體錯誤代碼

    新浪網的錯誤碼為數字類型的字符串,相比阿里云的錯誤碼要簡短不少,并且對程序更加友好,也是我個人更推薦的設計。

    業務錯誤碼

    結合市面上這些優秀的開放 API 錯誤碼設計,以及我在實際開發中的工作總結,我設計的錯誤碼規則如下:

    業務錯誤碼由 8 位純數字組成,類型為 int

    業務錯誤碼示例格式:40001002

    錯誤碼說明:

    1-3 位4-5 位6-8 位
    40001002
    HTTP 狀態碼組件編號組件內部錯誤碼

    錯誤碼設計為純數字主要是為了程序中使用起來更加方便,比如根據錯誤碼計算 HTTP 狀態碼,只需要通過簡單的數學取模計算就能做到。

    使用兩位數字來標記不同組件,最多能表示 99 個組件,即使項目全部采用微服務開發,一般來說也是足夠用的。

    最后三位代表組件內部錯誤碼,最多能表示 1000 個錯誤。其實通常來說一個組件內部是用不上這么多錯誤的,如果組件較小,完全可以設計成兩位數字。

    另外,有些廠商中還會設計一些公共的錯誤碼,可以稱為「全局錯誤碼」,這些錯誤碼在各組件間通用,以此來減少定義重復錯誤。在我們的錯誤碼設計中,可以將組件編號為 00 的標記為全局錯誤碼,其他組件編號從 01 開始。

    錯誤格式

    有了錯誤碼,還需要定義錯誤響應格式,設計一個標準的 API 錯誤響應格式如下:

    {
    	"code": 50000000,
    	"message": "系統錯誤",
    	"reference": "https://github.com/jianghushinian/gokit/tree/main/errors"
    }

    code 即為錯誤碼,message 為錯誤信息,reference 則是錯誤文檔地址,用來告知用戶如何解決這個錯誤,對標的是阿里云錯誤響應中的 Recommend 字段。

    錯誤碼實現

    因為每一個錯誤碼和錯誤信息以及錯誤文檔地址都是一一對應的,所以我們需要一個對象來保存這些信息,在 Go 中可以使用結構體。

    可以設計如下結構體:

    type apiCode struct {
    	code int
    	msg  string
    	ref  string
    }

    這是一個私有結構體,外部項目要想使用,則需要一個構造函數:

    func NewAPICode(code int, message string, reference ...string) APICoder {
    	ref := ""
    	if len(reference) > 0 {
    		ref = reference[0]
    	}
    	return &apiCode{
    		code: code,
    		msg:  message,
    		ref:  ref,
    	}
    }

    其中 reference 被設計為可變參數,如果不傳則默認為空。

    NewAPICode 返回值 APICoder 是一個接口,這在 Go 中是一種慣用做法。通過接口可以解耦,方便依賴 apiCode 的代碼編寫測試,用戶可以對 APICoder 進行 Mock;另一方面,我們稍后會為 apiCode 實現對應的錯誤包,使用接口來表示錯誤碼可以方便用戶定義自己的 apiCode 類型。

    為了便于使用,apiCode 提供了如下幾個能力:

    func (a *apiCode) Code() int {
    	return a.code
    }
    func (a *apiCode) Message() string {
    	return a.msg
    }
    func (a *apiCode) Reference() string {
    	return a.ref
    }
    func (a *apiCode) HTTPStatus() int {
    	v := a.Code()
    	for v >= 1000 {
    		v /= 10
    	}
    	return v
    }

    至此 APICoder 接口接口的定義也就有了:

    type APICoder interface {
    	Code() int
    	Message() string
    	Reference() string
    	HTTPStatus() int
    }

    apiCode 則實現了 APICoder 接口。

    現在我們可以通過如下方式創建錯誤碼結構體對象:

    var (
    	CodeBadRequest   = NewAPICode(40001001, "請求不合法")
    	CodeUnknownError = NewAPICode(50001001, "系統錯誤", "https://github.com/jianghushinian/gokit/tree/main/errors")
    )

    錯誤包

    設計好了錯誤碼,并不能直接使用,我們還需要一個與之配套的錯誤包來簡化錯誤碼的使用。

    錯誤包功能

    錯誤包要能夠完美支持上面設計的錯誤碼。所以需要使用 APICoder 來構造錯誤對象。

    錯誤包應該能夠查看原始錯誤原因。這就需要實現 Unwrap 方法,Wrap/Unwrap 方法是在 Go 1.13 中被加入進 errors 包的,目的是能夠處理嵌套錯誤。

    錯誤包應該能夠支持對內對外展示不同信息。這就需要實現 Format 方法,根據需要可以將錯誤格式化成不同輸出。

    錯誤包應該能夠支持展示堆棧信息。這對 Debug 來說相當重要,也是 Go 自帶的 errors 包不足的地方。

    為了方便在日志中記錄結構化錯誤信息,錯誤包還要能夠支持 JSON 序列化。這需要實現 MarshalJSON/UnmarshalJSON 兩個方法。

    錯誤包設計

    一個錯誤對象結構體設計如下:

    type apiError struct {
    	coder APICoder
    	cause error
    	*stack
    }

    其中 coder 用來保存實現了 APICoder 接口的對象,cause 用來記錄錯誤原因,stack 用來展示錯誤堆棧。

    錯誤對象的構造函數如下:

    var WrapC = NewAPIError
    func NewAPIError(coder APICoder, cause ...error) error {
    	var c error
    	if len(cause) > 0 {
    		c = cause[0]
    	}
    	return &apiError{
    		coder: coder,
    		cause: c,
    		stack: callers(),
    	}
    }

    NewAPIError 通過 APICoder 來創建錯誤對象,第二個參數為一個可選的錯誤原因。

    其實構造一個錯誤對象也就是對一個錯誤進行 Wrap 的過程,所以我還為構造函數 NewAPIError 定義了一個別名 WrapC,表示使用錯誤碼將一個錯誤包裝成一個新的錯誤。

    一個錯誤對象必須要實現 Error 方法:

    func (a *apiError) Error() string {
    	return fmt.Sprintf("[%d] - %s", a.coder.Code(), a.coder.Message())
    }

    默認情況下,獲取到的錯誤內容只包含錯誤碼 Code 和錯誤信息 Message。

    為了方便獲取被包裝錯誤的原始錯誤,還要實現 Unwrap 方法:

    func (a *apiError) Unwrap() error {
    	return a.cause
    }

    為了能在打印或寫入日志時展示不同信息,則要實現 Format 方法:

    func (a *apiError) Format(s fmt.State, verb rune) {
    	switch verb {
    	case 'v':
    		if s.Flag('+') {
    			str := a.Error()
    			if a.Unwrap() != nil {
    				str += " " + a.Unwrap().Error()
    			}
    			_, _ = io.WriteString(s, str)
    			a.stack.Format(s, verb)
    			return
    		}
    		if s.Flag('#') {
    			cause := ""
    			if a.Unwrap() != nil {
    				cause = a.Unwrap().Error()
    			}
    			data, _ := json.Marshal(errorMessage{
    				Code:      a.coder.Code(),
    				Message:   a.coder.Message(),
    				Reference: a.coder.Reference(),
    				Cause:     cause,
    				Stack:     fmt.Sprintf("%+v", a.stack),
    			})
    			_, _ = io.WriteString(s, string(data))
    			return
    		}
    		fallthrough
    	case 's':
    		_, _ = io.WriteString(s, a.Error())
    	case 'q':
    		_, _ = fmt.Fprintf(s, "%q", a.Error())
    	}
    }

    Format 方法能夠支持在使用 fmt.Printf("%s", apiError) 格式化輸出時打印定制化的信息。

    Format 方法支持的不同格式輸出如下:

    格式占位符輸出信息
    %s錯誤碼、錯誤信息
    %v錯誤碼、錯誤信息,與 %s 等價
    %+v錯誤碼、錯誤信息、錯誤原因、錯誤堆棧
    %#vJSON 格式的 錯誤碼、錯誤信息、錯誤文檔地址、錯誤原因、錯誤堆棧
    %q在 錯誤碼、錯誤信息 外層增加了一個雙引號

    這些錯誤格式基本上能滿足所有業務開發中的需求了,如果還有其他格式需要,則可以在此基礎上進一步開發 Format 方法。

    用來進行 JSON 序列化和反序列化的 MarshalJSON/UnmarshalJSON 方法實現如下:

    func (a *apiError) MarshalJSON() ([]byte, error) {
    	return json.Marshal(&errorMessage{
    		Code:      a.coder.Code(),
    		Message:   a.coder.Message(),
    		Reference: a.coder.Reference(),
    	})
    }
    func (a *apiError) UnmarshalJSON(data []byte) error {
    	e := &errorMessage{}
    	if err := json.Unmarshal(data, e); err != nil {
    		return err
    	}
    	a.coder = NewAPICode(e.Code, e.Message, e.Reference)
    	return nil
    }
    type errorMessage struct {
    	Code      int    `json:"code"`
    	Message   string `json:"message"`
    	Reference string `json:"reference,omitempty"`
    	Cause     string `json:"cause,omitempty"`
    	Stack     string `json:"stack,omitempty"`
    }

    為了不對外部暴露敏感信息,對外的 HTTP API 只會返回 CodeMessageReference(可選)三個字段,對內需要額外展示錯誤原因以及錯誤堆棧。所以 errorMessageReferenceCauseStack 字段都帶有 omitempty 屬性,這樣在 MarshalJSON 時只會序列化 CodeMessageReference 這三個字段。

    至此,我們就實現了錯誤包的設計。

    錯誤碼及錯誤包的使用

    使用示例

    通過上面的講解,我們了解了錯誤碼和錯誤包的設計規范,接下來看看如何使用它們。這里以錯誤碼及錯誤包在 Gin 中的使用為例進行講解。

    使用 Gin 構建一個簡單的 Web Server 如下:

    package main
    import (
    	"errors"
    	"fmt"
    	"strconv"
    	"github.com/gin-gonic/gin"
    	apierr "github.com/jianghushinian/gokit/errors"
    )
    var (
    	ErrAccountNotFound = errors.New("account not found")
    	ErrDatabase        = errors.New("database error")
    )
    var (
    	CodeBadRequest   = NewAPICode(40001001, "請求不合法")
    	CodeNotFound     = NewAPICode(40401001, "資源未找到")
    	CodeUnknownError = NewAPICode(50001001, "系統錯誤", "https://github.com/jianghushinian/gokit/tree/main/errors")
    )
    type Account struct {
    	ID   int    `json:"id"`
    	Name string `json:"name"`
    }
    func AccountOne(id int) (*Account, error) {
    	for _, v := range accounts {
    		if id == v.ID {
    			return &v, nil
    		}
    	}
    	// 模擬返回數據庫錯誤
    	if id == 500 {
    		return nil, ErrDatabase
    	}
    	return nil, ErrAccountNotFound
    }
    var accounts = []Account{
    	{ID: 1, Name: "account_1"},
    	{ID: 2, Name: "account_2"},
    	{ID: 3, Name: "account_3"},
    }
    func ShowAccount(c *gin.Context) {
    	id := c.Param("id")
    	aid, err := strconv.Atoi(id)
    	if err != nil {
    		// 將 errors 包裝成 APIError 返回
    		ResponseError(c, apierr.WrapC(CodeBadRequest, err))
    		return
    	}
    	account, err := AccountOne(aid)
    	if err != nil {
    		switch {
    		case errors.Is(err, ErrAccountNotFound):
    			err = apierr.NewAPIError(CodeNotFound, err)
    		case errors.Is(err, ErrDatabase):
    			err = apierr.NewAPIError(CodeUnknownError, fmt.Errorf("account %d: %w", aid, err))
    		}
    		ResponseError(c, err)
    		return
    	}
    	ResponseOK(c, account)
    }
    func main() {
    	r := gin.Default()
    	r.GET("/accounts/:id", ShowAccount)
    	if err := r.Run(":8080"); err != nil {
    		panic(err)
    	}
    }

    在這個 Web Server 中定義了一個 ShowAccount 函數,用來處理獲取賬號邏輯,在 ShowAccount 內部程序執行成功返回 ResponseOK(c, account),失敗則返回 ResponseError(c, err)

    在處理返回失敗的響應時,都會通過 apierr.WrapCapierr.NewAPIError 將底層函數返回的初始錯誤進行一層包裝,根據錯誤級別,包裝成不同的錯誤碼進行返回。

    其中 ResponseOKResponseError 定義如下:

    func ResponseOK(c *gin.Context, spec interface{}) {
    	if spec == nil {
    		c.Status(http.StatusNoContent)
    		return
    	}
    	c.JSON(http.StatusOK, spec)
    }
    func ResponseError(c *gin.Context, err error) {
    	log(err)
    	e := apierr.ParseCoder(err)
    	httpStatus := e.HTTPStatus()
    	if httpStatus >= 500 {
    		// send error msg to email/feishu/sentry...
    		go fakeSendErrorEmail(err)
    	}
    	c.AbortWithStatusJSON(httpStatus, err)
    }
    // log 打印錯誤日志,輸出堆棧
    func log(err error) {
    	fmt.Println("========== log start ==========")
    	fmt.Printf("%+v\n", err)
    	fmt.Println("========== log end ==========")
    }
    // fakeSendErrorEmail 模擬將錯誤信息發送到郵件,JSON 格式
    func fakeSendErrorEmail(err error) {
    	fmt.Println("========== error start ==========")
    	fmt.Printf("%#v\n", err)
    	fmt.Println("========== error end ==========")
    }

    ResponseOK 其實就是 Gin 框架的正常返回,ResponseError 則專門用來處理并返回 API 錯誤。

    ResponseError 中首先通過 log(err) 來記錄錯誤日志,在其內部使用 fmt.Printf("%+v\n", err) 進行打印。

    之后我們還對 HTTP 狀態碼進行了判斷,大于 500 的錯誤將會發送郵件通知,這里使用 fmt.Printf("%#v\n", err) 進行模擬。

    其中 apierr.ParseCoder(err) 能夠從一個錯誤對象中獲取到實現了 APICoder 的錯誤碼對象,實現如下:

    func ParseCoder(err error) APICoder {
    	for {
    		if e, ok := err.(interface {
    			Coder() APICoder
    		}); ok {
    			return e.Coder()
    		}
    		if errors.Unwrap(err) == nil {
    			return CodeUnknownError
    		}
    		err = errors.Unwrap(err)
    	}
    }

    這樣,我們就能夠通過一個簡單的 Web Server 示例程序來演示如何使用錯誤碼和錯誤包了。

    可以通過 go run main.go 啟動這個 Web Server。

    先來看下在這個 Web Server 中一個正常的返回結果是什么樣,使用 cURL 來發送一個請求:curl http://localhost:8080/accounts/1

    客戶端得到如下響應結果:

    {
    	"id": 1,
    	"name": "account_1"
    }

    服務端打印正常的請求日志:

    go語言規范RESTful?API業務錯誤處理的方法是什么

    再來測試下請求一個不存在的賬號:curl http://localhost:8080/accounts/12

    客戶端得到如下響應結果:

    {
    	"code": 40401001,
    	"message": "資源未找到"
    }

    返回結果中沒有 reference 字段,是因為對于 reference 為空的情況,在 JSON 序列化過程中會被隱藏。

    服務端打印的錯誤日志如下:

    ========== log start ==========
    [40401001] - 資源未找到 account not found
    main.ShowAccount
            /app/errors/examples/main.go:56
    github.com/gin-gonic/gin.(*Context).Next
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/context.go:174
    github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/recovery.go:102
    github.com/gin-gonic/gin.(*Context).Next
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/context.go:174
    github.com/gin-gonic/gin.LoggerWithConfig.func1
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/logger.go:240
    github.com/gin-gonic/gin.(*Context).Next
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/context.go:174
    github.com/gin-gonic/gin.(*Engine).handleHTTPRequest
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/gin.go:620
    github.com/gin-gonic/gin.(*Engine).ServeHTTP
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/gin.go:576
    net/http.serverHandler.ServeHTTP
            /usr/local/go/src/net/http/server.go:2947
    net/http.(*conn).serve
            /usr/local/go/src/net/http/server.go:1991
    runtime.goexit
            /usr/local/go/src/runtime/asm_arm64.s:1165
    ========== log end ==========

    可以發現,錯誤日志中不僅打印了錯誤碼([40401001])和錯誤信息(資源未找到),還打印了錯誤原因(account not found)以及下面的錯誤堆棧。

    如此清晰的錯誤日志得益于我們實現的 Format 函數的強大功能。

    現在再來觸發一個 HTTP 狀態碼為 500 的錯誤響應:curl http://localhost:8080/accounts/500

    客戶端得到如下響應結果:

    {
    	"code": 50001001,
    	"message": "系統錯誤",
    	"reference": "https://github.com/jianghushinian/gokit/tree/main/errors"
    }

    這次得到一個帶有 reference 字段的完整錯誤響應。

    服務端打印的錯誤日志如下:

    ========== log start ==========
    [50001001] - 系統錯誤 account 500: database error
    main.ShowAccount
            /app/errors/examples/main.go:58
    github.com/gin-gonic/gin.(*Context).Next
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/context.go:174
    github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/recovery.go:102
    github.com/gin-gonic/gin.(*Context).Next
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/context.go:174
    github.com/gin-gonic/gin.LoggerWithConfig.func1
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/logger.go:240
    github.com/gin-gonic/gin.(*Context).Next
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/context.go:174
    github.com/gin-gonic/gin.(*Engine).handleHTTPRequest
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/gin.go:620
    github.com/gin-gonic/gin.(*Engine).ServeHTTP
            /go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/gin.go:576
    net/http.serverHandler.ServeHTTP
            /usr/local/go/src/net/http/server.go:2947
    net/http.(*conn).serve
            /usr/local/go/src/net/http/server.go:1991
    runtime.goexit
            /usr/local/go/src/runtime/asm_arm64.s:1165
    ========== log end ==========
    [GIN] 2023/03/05 - 02:02:28 | 500 |     426.292µs |       127.0.0.1 | GET      "/accounts/500"
    ========== error start ==========
    {"code":50001001,"message":"系統錯誤","reference":"https://github.com/jianghushinian/gokit/tree/main/errors","cause":"account 500: database error","stack":"\nmain.ShowAccount\n\t/app/errors/examples/main.go:58\ngithub.com/gin-gonic/gin.(*Context).Next\n\t/go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/context.go:174\ngithub.com/gin-gonic/gin.CustomRecoveryWithWriter.func1\n\t/go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/recovery.go:102\ngithub.com/gin-gonic/gin.(*Context).Next\n\t/go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/context.go:174\ngithub.com/gin-gonic/gin.LoggerWithConfig.func1\n\t/go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/logger.go:240\ngithub.com/gin-gonic/gin.(*Context).Next\n\t/go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/context.go:174\ngithub.com/gin-gonic/gin.(*Engine).handleHTTPRequest\n\t/go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/gin.go:620\ngithub.com/gin-gonic/gin.(*Engine).ServeHTTP\n\t/go/pkg/mod/github.com/gin-gonic/gin@v1.9.0/gin.go:576\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2947\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go:1991\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_arm64.s:1165"}
    ========== error end ==========

    這一次除了 log 函數打印的日志,還能看到 fakeSendErrorEmail 函數打印的日志,正是一個 JSON 格式的結構化日志。

    以上便是我們設計的錯誤碼及錯誤包在實際開發場景中的應用。

    使用建議

    根據我的經驗,總結了一些錯誤碼及錯誤包的使用建議,現在將其分享給你。

    使用盡量少的 HTTP 狀態碼

    HTTP 狀態碼大概分為 5 大類,分別是 1XX、2XX、3XX、4XX、5XX。根據我的實際工作經驗,我們并不會使用全部的狀態碼,最常用的狀態碼不超過 10 個。

    所以即使我們設計的業務錯誤碼支持攜帶 HTTP 狀態碼,但也不推薦使用過多的 HTTP 狀態碼,以免加重前端工作量。

    推薦在錯誤碼中使用的 HTTP 狀態碼如下:

    • 400: 請求不合法

    • 401: 認證失敗

    • 403: 授權失敗

    • 404: 資源未找到

    • 500: 系統錯誤

    其中 4XX 代表客戶端錯誤,而如果是服務端錯誤,則統一使用 500 狀態碼,具體錯誤原因可以通過業務錯誤碼定位。

    使用中間件來記錄錯誤日志

    由于我們設計的錯誤包支持 Unwrap 操作,所以建議出現錯誤時的處理流程如下:

    • 最底層代碼遇到錯誤時通過 errors.New/fmt.Errorf 來創建一個錯誤對象,然后將錯誤返回(可選擇性的記錄一條日志)。

    func Query(id int) (obj, error) {
        // do something
        return nil, fmt.Errorf("%d not found", id)
    }
    • 中間過程中處理函數遇到下層函數返回的錯誤,不做任何額外處理,直接將其向上層返回。

    if err != nil {
        return err
    }
    • 在處理用戶請求的 Handler 函數中(如 ShowAccount)通過 apierr.WrapC 將錯誤包裝成一個 APIError 返回。

    if err != nil {
        return apierr.WrapC(CodeNotFound, err)
    }
    • 最上層代碼通過在框架層面實現的中間件(如實現一個 after hook middleware)來統一處理錯誤,打印完整錯誤日志、發送郵件提醒等,并將安全的錯誤信息返回給前端。如我們實現的 ResponseError 函數功能。

    以上就是關于“go語言規范RESTful API業務錯誤處理的方法是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    通州区| 云林县| 台前县| 手游| 锦屏县| 揭阳市| 铅山县| 鸡西市| 新乐市| 重庆市| 百色市| 肇源县| 尼勒克县| 临武县| 大同市| 开封市| 仁布县| 额尔古纳市| 施秉县| 宜兰市| 阳新县| 额敏县| 积石山| 南木林县| 孝感市| 信丰县| 龙胜| 新竹市| 盐亭县| 华宁县| 内江市| 罗源县| 晋江市| 高雄县| 梁山县| 定兴县| 永福县| 济宁市| 西青区| 眉山市| 托里县|