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

回顾

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

背景

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

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

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

原始的:

go 复制代码
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 接口规范动作,做逻辑抽象

go 复制代码
package response

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

model.go

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

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

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

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)
}

如何使用封装的工具包?

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

结束语

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

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

相关推荐
CopyLower6 分钟前
在 Spring Boot 中实现 WebSockets
spring boot·后端·iphone
.生产的驴1 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
景天科技苑1 小时前
【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
开发语言·后端·rust·match·enum·枚举与模式匹配·rust枚举与模式匹配
追逐时光者2 小时前
MongoDB从入门到实战之Docker快速安装MongoDB
后端·mongodb
方圆想当图灵2 小时前
深入理解 AOP:使用 AspectJ 实现对 Maven 依赖中 Jar 包类的织入
后端·maven
豌豆花下猫2 小时前
Python 潮流周刊#99:如何在生产环境中运行 Python?(摘要)
后端·python·ai
嘻嘻嘻嘻嘻嘻ys2 小时前
《Spring Boot 3 + Java 17:响应式云原生架构深度实践与范式革新》
前端·后端
异常君2 小时前
线程池隐患解析:为何阿里巴巴拒绝 Executors
java·后端·代码规范
mazhimazhi2 小时前
GC垃圾收集时,居然还有用户线程在奔跑
后端·面试
Python私教2 小时前
基于 Requests 与 Ollama 的本地大模型交互全栈实践指南
后端