「容器管理系统」开发篇: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 的使用,中间件的验证

相关推荐
陈随易32 分钟前
Bun v1.4 更新总结:把浏览器、图片、定时任务和工程工具都装进一个运行时
前端·后端·程序员
人间凡尔赛1 小时前
2026 后端架构三驾马车:Wasm 容器上 K8s、存算分离与 AI 原生
后端·云原生·架构
凤山老林2 小时前
动态 i18n 体系落地:Spring Boot 多租户热加载与前后端协同实践
java·spring boot·后端·i18n
东风破_2 小时前
TypeScript 高级类型进阶:keyof、Exclude、Record 与类型组合思想
前端·后端·typescript
凤山老林2 小时前
数据库读写分离与动态路由实战:Spring Boot + ShardingSphere-JDBC 生产配置
数据库·spring boot·后端·分库分表·sharding-jdbc
Zane19943 小时前
调用了 async 函数却没执行?一文讲透协程、event loop 与 await
后端·python
Zane19943 小时前
HashMap 为什么要在长度16、容量必须是2的幂这些细节上较劲
java·后端
掘金者阿豪4 小时前
若依启动突然报 Redis MISCONF?一次从应用报错到磁盘爆满的完整排查记录
后端
boooooooom4 小时前
尝尝咸淡:从朴素 RAG 到 Graph RAG——一个烹饪问答系统的三层检索升级之路
前端·后端·llm
福大大架构师每日一题5 小时前
ragflow v0.27.0 发布:Go 后端全面对齐、智能体搜索与知识编译大升级,超全更新解析
开发语言·后端·golang