likes
comments
collection
share

「容器管理系统」开发篇:2. 封装gin统一返回JSON

作者站长头像
站长
· 阅读数 19

回顾

项目已开源:基于 Golang 的 容器管理系统

背景

在上节说到:我们已经初始化了 Gin 框架的初始化和一些基本配置,为什么要拉出来作为一篇文章单独说明呢?

大家都知道,在使用类似的 web 框架时,比如:Gin、Beego、Iris、Echo,都需要做一些初始化动作,和一些通用的工具包,开发起来也是挺费时间的,所以就单拉出来讲解。

下面咱们就进入正题:为什么要封装统一返回?

原始的:

func MenuResp(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "msg":  "",
        "data": menuResp,
    })
    return
}

虽然看这个也比较简洁,但是这里有几个问题

  1. 自定义返回提示每次都需要书写,不方便复用
  2. 每次都需要定义 mapKey
  3. 记录日志不方便
  4. 无法链路追踪
  5. 日志监控比较繁琐

综合以上几个问题,封装统一返回的工具包在开发中就是势在必行的一个行为。

统一封装的好处

  • 高可用,复用
  • 调用简洁
  • 日志监控,链路追踪
  • 代码优美

定义

注意: 封装工具包使用的: ErrorMsg 是提前定义的返回状态和返回信息,这里只用于参考,具体使用请参考开源项目:基于 Golang 的 容器管理系统

封装统一的返回JOSN,咱们采取的是 接口定义规则动作,通过 struct 来实现和复用接口动作,实现逻辑复用。

工具包名定义为:response 总共定义了三个文件:

  • type.go
  • model.go
  • return.go

type.go

定义 Responses 接口规范动作,做逻辑抽象

package response

type Responses interface {
    SetCode(int32)
    SetTraceID(string)
    SetMsg(string)
    SetInfo(string)
    SetData(interface{})
    SetSuccess(bool)
    Clone() Responses  // 初始化/重置
}

model.go

实现定义的接口动作,并定义要返回的结构体

package response

type Response struct {
    RequestId string `json:"requestId,omitempty"`
    Code      int32  `json:"code,omitempty"`
    Info      string `json:"info,omitempty"`
    Msg       string `json:"msg,omitempty"`
    Status    string `json:"status,omitempty"`
}

type response struct {
    Response
    Data any `json:"data"`
}

func (e *response) SetTraceID(id string) {
    e.RequestId = id
}

func (e *response) SetMsg(msg string) {
    e.Msg = msg
}

func (e *response) SetInfo(info string) {
    e.Info = info
}

func (e *response) SetCode(code int32) {
    e.Code = code
}

func (e *response) SetSuccess(success bool) {
    if !success {
       e.Status = "error"
    }
}

type Page struct {
    Count     int `json:"count"`
    PageIndex int `json:"page_index"`
    PageSize  int `json:"page_size"`
}

type page struct {
    Page
    List any `json:"list"`
}

func (e *response) SetData(data any) {
    e.Data = data
}

func (e response) Clone() Responses {
    return &e
}

return.go

实现封装操作,统一返回的真实业务逻辑

package response

import (
    "github.com/CodeLine-95/go-cloud-native/internal/pkg/xlog"
    "github.com/CodeLine-95/go-cloud-native/tools/logz"
    "github.com/CodeLine-95/go-cloud-native/tools/traceId"
    "github.com/gin-gonic/gin"
    "net/http"
)

var Default = &response{}

// Error 失败数据处理
func Error(c *gin.Context, code int, err error, msg string) {
    res := Default.Clone()
    res.SetInfo(msg)
    if err != nil {
       res.SetInfo(err.Error())
    }
    if msg != "" {
       res.SetMsg(msg)
    }
    res.SetTraceID(traceId.GetTraceId(c))
    res.SetCode(int32(code))
    res.SetSuccess(false)
    // 记录日志
    xlog.Error(traceId.GetLogContext(c, msg, logz.F("err", err), logz.F("response", res)))
    // 写入上下文
    c.Set("result", res)
    // 返回结果集
    c.AbortWithStatusJSON(http.StatusOK, res)
}

// OK 通常成功数据处理
func OK(c *gin.Context, data any, msg string) {
    res := Default.Clone()
    res.SetData(data)
    res.SetSuccess(true)
    if msg != "" {
       res.SetMsg(msg)
       res.SetInfo(msg)
    }
    res.SetTraceID(traceId.GetTraceId(c))
    res.SetCode(http.StatusOK)
    // 记录日志
    xlog.Info(traceId.GetLogContext(c, msg, logz.F("response", res)))
    // 写入上下文
    c.Set("result", res)
    c.AbortWithStatusJSON(http.StatusOK, res)
}

// PageOK 分页数据处理
func PageOK(c *gin.Context, result any, count int, pageIndex int, pageSize int, msg string) {
    var res page
    res.List = result
    res.Count = count
    res.PageIndex = pageIndex
    res.PageSize = pageSize
    OK(c, res, msg)
}

// Custum 兼容函数
func Custum(c *gin.Context, data gin.H) {
    data["requestId"] = traceId.GetTraceId(c)
    c.Set("result", data)
    c.AbortWithStatusJSON(http.StatusOK, data)
}

如何使用封装的工具包?

  • 错误返回
response.Error(c, constant.ErrorDB, err, constant.ErrorMsg[constant.ErrorDB])
  • 成功返回
response.OK(c, nil, constant.ErrorMsg[constant.Success])
  • 分页成功返回
response.PageOK(c, roleResp, len(roleResp), params.Page, params.PageSize, constant.ErrorMsg[constant.Success])

结束语

  • 为什么要封装工具包?
  • 封装工具包的优势
  • 工具包的具体实现
  • 如何使用封装好的工具包?

下一节预告: JWT Token 的使用,中间件的验证