Golang 快速标准化后端错误码

235 阅读1分钟

介绍

rk-boot 官网:rkdev.info

本文通过一个完整的例子,介绍通过 rk-boot 实现标准化的后台服务错误码。

我们将会使用 rk-boot/v2 来启动 gin-gonic/gin 后台微服务进行展示。

默认使用 Google 样式的错误。

  • Google 样式
{
  "error":{
    "code":500,
    "status":"Internal Server Error",
    "message":"Panic occurs",
    "details":[
      "panic manually"
    ]
  }
}
  • Amazon 样式
{
    "response":{
        "errors":[
            {
                "error":{
                    "code":500,
                    "status":"Internal Server Error",
                    "message":"Panic occurs",
                    "details":[
                        "panic manually"
                    ]
                }
            }
        ]
    }
}
  • 自定义样式请参照后面的例子。

其他微服务文档请参考如下链接:

Web 框架官方文档
gin-gonic/gin官方文档
gRPC官方文档
labstack/echo官方文档
gogf/gf官方文档
gofiber/fiber官方文档
zeromicro/go-zero官方文档
gorilla/mux官方文档

安装

$ go get github.com/rookie-ninja/rk-boot/v2
$ go get github.com/rookie-ninja/rk-gin/v2

快速开始

1.创建 boot.yaml

boot.yaml 文件描述了 Gin 框架启动的元信息,rk-boot 通过读取 boot.yaml 来启动 Gin。

---
gin:
  - name: greeter
    port: 8080
    enabled: true

2.创建 main.go

让 /v1/greeter 返回一个错误。

package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"github.com/rookie-ninja/rk-boot/v2"
	"github.com/rookie-ninja/rk-entry/v2/error"
	"github.com/rookie-ninja/rk-gin/v2/boot"
	"net/http"
)

func main() {
	// Create a new boot instance.
	boot := rkboot.NewBoot()
	
	// Register handler
	entry := rkgin.GetGinEntry("greeter")
	entry.Router.GET("/v1/greeter", Greeter)

	// Bootstrap
	boot.Bootstrap(context.TODO())

	boot.WaitForShutdownSig(context.TODO())
}

func Greeter(ctx *gin.Context) {
	err := rkmid.GetErrorBuilder().New(http.StatusAlreadyReported, "Trigger manually!",
		"This is detail.",
		false, -1,
		0.1)

	ctx.JSON(http.StatusAlreadyReported, err)
}

3.验证

$ curl "localhost:8080/v1/greeter?name=rk-dev"
{
    "error":{
        "code":208,
        "status":"Already Reported",
        "message":"Trigger manually!",
        "details":[
            "This is detail.",
            false,
            -1,
            0.1
        ]
    }
}

Amazon 样式错误

需要在 boot.yaml 中定义错误模型为 amazon。

  • boot.yaml
gin:
  - name: greeter
    port: 8080
    enabled: true
    middleware:
      errorModel: amazon
{
    "response":{
        "errors":[
            {
                "error":{
                    "code":208,
                    "status":"Already Reported",
                    "message":"Trigger manually!",
                    "details":[
                        "This is detail.",
                        false,
                        -1,
                        0.1
                    ]
                }
            }
        ]
    }
}

自定义样式

实现如下两个接口,并注册到 Entry 中。

type ErrorInterface interface {
	Error() string

	Code() int

	Message() string

	Details() []interface{}
}

type ErrorBuilder interface {
	New(code int, msg string, details ...interface{}) ErrorInterface

	NewCustom() ErrorInterface
}

main.go

package main

import (
  "context"
  "fmt"
  "github.com/gin-gonic/gin"
  "github.com/rookie-ninja/rk-boot/v2"
  "github.com/rookie-ninja/rk-entry/v2/error"
  "github.com/rookie-ninja/rk-entry/v2/middleware"
  "github.com/rookie-ninja/rk-gin/v2/boot"
  "net/http"
)

func main() {
  // Create a new boot instance.
  boot := rkboot.NewBoot()

  // Register handler
  entry := rkgin.GetGinEntry("greeter")
  entry.Router.GET("/v1/greeter", Greeter)

  // Bootstrap
  boot.Bootstrap(context.TODO())

  // Set default error builder after bootstrap
  rkmid.SetErrorBuilder(&MyErrorBuilder{})

  boot.WaitForShutdownSig(context.TODO())
}

func Greeter(ctx *gin.Context) {
  err := rkmid.GetErrorBuilder().New(http.StatusAlreadyReported, "Trigger manually!",
    "This is detail.",
    false, -1,
    0.1)

  ctx.JSON(http.StatusAlreadyReported, err)
}

type MyError struct {
  ErrCode    int
  ErrMsg     string
  ErrDetails []interface{}
}

func (m MyError) Error() string {
  return fmt.Sprintf("%d-%s", m.ErrCode, m.ErrMsg)
}

func (m MyError) Code() int {
  return m.ErrCode
}

func (m MyError) Message() string {
  return m.ErrMsg
}

func (m MyError) Details() []interface{} {
  return m.ErrDetails
}

type MyErrorBuilder struct{}

func (m *MyErrorBuilder) New(code int, msg string, details ...interface{}) rkerror.ErrorInterface {
  return &MyError{
    ErrCode:    code,
    ErrMsg:     msg,
    ErrDetails: details,
  }
}

func (m *MyErrorBuilder) NewCustom() rkerror.ErrorInterface {
  return &MyError{
    ErrCode:    http.StatusInternalServerError,
    ErrMsg:     "Internal Error",
    ErrDetails: []interface{}{},
  }
}

验证

$ curl "localhost:8080/v1/greeter?name=rk-dev"
{
    "ErrCode":208,
    "ErrMsg":"Trigger manually!",
    "ErrDetails":[
        "This is detail.",
        false,
        -1,
        0.1
    ]
}

原理

当通过 rk-boot 启动微服务的时候,rk-boot 默认会基于 boot.yaml 里的配置,创建一个 ErrorBuilder 实例。 用户只要通过 rkmid.GetErrorBuilder().New() 方法创建错误即可。

rk-boot 默认的 panic handler 中间件也会使用 ErrorBuilder 返回统一格式的错误,保证整个服务的错误码一致。