回顾
项目已开源:基于 Golang 的 容器管理系统
背景
在上节说到:我们已经初始化了 Gin
框架的初始化和一些基本配置,为什么要拉出来作为一篇文章单独说明呢?
大家都知道,在使用类似的
web
框架时,比如:Gin、Beego、Iris、Echo
,都需要做一些初始化动作,和一些通用的工具包,开发起来也是挺费时间的,所以就单拉出来讲解。
下面咱们就进入正题:为什么要封装统一返回?
原始的:
go
func MenuResp(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "",
"data": menuResp,
})
return
}
虽然看这个也比较简洁,但是这里有几个问题:
- 自定义返回提示每次都需要书写,不方便复用
- 每次都需要定义
mapKey
- 记录日志不方便
- 无法链路追踪
- 日志监控比较繁琐
综合以上几个问题,封装统一返回的工具包在开发中就是势在必行的一个行为。
统一封装的好处
- 高可用,复用
- 调用简洁
- 日志监控,链路追踪
- 代码优美
定义
注意: 封装工具包使用的:
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
的使用,中间件的验证