找回密码
 立即注册
首页 业界区 业界 Gin 框架中的规范响应格式设计与实现

Gin 框架中的规范响应格式设计与实现

滥眩 昨天 23:05
在现代Web应用开发中,统一和规范化的API响应格式对于前后端协作至关重要。今天,我们来探讨如何在Gin框架中设计一套既实用又易于维护的响应格式规范。
为什么需要统一的响应格式?

首先,让我们思考一个问题:为什么要统一API响应格式?

  • 前后端协作效率:一致的响应格式让前端开发者能以统一的方式处理服务端响应
  • 错误处理简化:标准化的错误码和消息便于统一处理各种异常情况
  • 接口文档维护:规范化响应减少文档编写工作量
  • 客户端适配:移动端或其他客户端可以复用相同的响应解析逻辑
设计统一的响应结构

让我们从最基础的响应结构开始。在本文的示例项目中,我采用了如下的响应结构:
  1. type baseResponse struct {
  2.     Code int    `json:"code"`
  3.     Msg  string `json:"msg"`
  4.     Data any    `json:"data"`
  5. }
复制代码
这个结构包含了三个基本元素:

  • Code: HTTP状态码或业务状态码,表示请求的执行结果
  • Msg: 人类可读的消息,描述请求的执行状态。(吐槽一下,见过各种项目,有的用"message", 有的用""messages", 调用方稍不留神就写错了,所以干脆用缩写)
  • Data: 实际的业务数据,根据不同接口返回不同内容
有的业务还需要返回timestamp或者trace_id之类的内容,可以根据实际需求来修改。
实现响应处理工具类

有了基础结构后,我们可以构建一个响应处理工具类。在我的项目中,pkg/response/response.go 文件实现了多种常用的响应方法:
  1. // Success 返回200状态码, 默认返回成功
  2. func Success(c *gin.Context, data any, opts *ResponseOption) {
  3.     if opts == nil {
  4.         opts = &ResponseOption{
  5.             Msg: "success",
  6.         }
  7.     }
  8.     c.JSON(http.StatusOK, baseResponse{
  9.         Code: http.StatusOK,
  10.         Msg:  opts.Msg,
  11.         Data: data,
  12.     })
  13. }
复制代码
通过这种方式,我们可以针对不同的HTTP状态码提供专门的响应方法:

  • Success: 正常业务响应
  • BadRequest: 参数校验失败
  • Unauthorized: 权限校验失败
  • NotFound: 资源不存在
  • InternalServerError: 服务器内部错误
处理异常情况

仅仅处理正常的业务响应是不够的,我们还需要统一拦截异常进行处理,否则异常和未注册路由都不会返回我们需要的格式。这里我用一个自定义的异常恢复中间件做异常捕获:
  1. func CustomRecovery() gin.HandlerFunc {
  2.     return func(c *gin.Context) {
  3.         defer func() {
  4.             if err := recover(); err != nil {
  5.                 var brokenPipe bool
  6.                 // 检测是否是连接中断
  7.                 if ne, ok := err.(*net.OpError); ok {
  8.                     if se, ok := ne.Err.(*os.SyscallError); ok {
  9.                         if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
  10.                             strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
  11.                             brokenPipe = true
  12.                         }
  13.                     }
  14.                 }
  15.                 // 获取堆栈信息
  16.                 stack := string(debug.Stack())
  17.                 if brokenPipe {
  18.                     c.Abort()
  19.                     response.InternalServerError(c, nil, &response.ResponseOption{Msg: "network abort"})
  20.                     return
  21.                 }
  22.                 slog.Error("exception catched", "error", err, "stack", stack)
  23.                 c.Abort()
  24.                 response.InternalServerError(c, nil, &response.ResponseOption{Msg: "server internal error"})
  25.             }
  26.         }()
  27.         c.Next()
  28.     }
  29. }
复制代码
这个中间件有几个亮点:

  • 连接中断处理:特别处理了 "broken pipe" 和 "connection reset by peer" 错误,避免客户端提前断开连接时产生冗余错误日志
  • 错误信息记录:记录错误详情和堆栈信息,便于问题排查
  • 统一错误响应:所有异常都以统一格式返回给客户端
路由未找到处理

除了异常处理,我们还需要处理请求路由不存在的情况:
  1. r.NoRoute(func(c *gin.Context) {
  2.     response.NotFound(c, nil, &response.ResponseOption{
  3.         Msg: "接口不存在",
  4.     })
  5. })
复制代码
这样,当用户请求不存在的接口时,也会收到格式统一的响应。
使用示例

在实际使用中,我们的控制器代码变得简洁明了:
  1. r.GET("/a1", func(c *gin.Context) {
  2.     response.Success(c, nil, nil)
  3. })
  4. r.GET("/a2", func(c *gin.Context) {
  5.     var respData = struct {
  6.         Name string
  7.     }{
  8.         Name: "hello",
  9.     }
  10.     response.Success(c, respData, &response.ResponseOption{
  11.         Msg: "how a successful response",
  12.     })
  13. })
复制代码
无论是在成功响应还是错误响应中,客户端收到的都是相同格式的JSON数据,极大地提升了开发体验。
补充-完整代码示例

项目结构:
  1. ├── go.mod
  2. ├── go.sum
  3. ├── main.go
  4. └── pkg
  5.     └── response
  6.         └── response.go
复制代码
响应类


  • pkg/response/response.go
  1. package response
  2. import (
  3.         "net/http"
  4.         "github.com/gin-gonic/gin"
  5. )
  6. type baseResponse struct {
  7.         Code int    `json:"code"`
  8.         Msg  string `json:"msg"`
  9.         Data any    `json:"data"`
  10. }
  11. type ResponseOption struct {
  12.         Msg string `json:"msg"`
  13. }
  14. // Success 返回200状态码, 默认返回成功
  15. func Success(c *gin.Context, data any, opts *ResponseOption) {
  16.         if opts == nil {
  17.                 opts = &ResponseOption{
  18.                         Msg: "success",
  19.                 }
  20.         }
  21.         c.JSON(http.StatusOK, baseResponse{
  22.                 Code: http.StatusOK,
  23.                 Msg:  opts.Msg,
  24.                 Data: data,
  25.         })
  26. }
  27. // SuccessCreated 返回201状态码, 表示创建成功。常用于新增数据
  28. func SuccessCreated(c *gin.Context, data any, opts *ResponseOption) {
  29.         if opts == nil {
  30.                 opts = &ResponseOption{
  31.                         Msg: "success",
  32.                 }
  33.         }
  34.         c.JSON(http.StatusCreated, baseResponse{
  35.                 Code: http.StatusCreated,
  36.                 Msg:  opts.Msg,
  37.                 Data: data,
  38.         })
  39. }
  40. // BadRequest 返回400错误, 常用于参数校验失败
  41. func BadRequest(c *gin.Context, data any, opts *ResponseOption) {
  42.         if opts == nil {
  43.                 opts = &ResponseOption{
  44.                         Msg: "bad request",
  45.                 }
  46.         }
  47.         c.JSON(http.StatusBadRequest, baseResponse{
  48.                 Code: http.StatusBadRequest,
  49.                 Msg:  opts.Msg,
  50.                 Data: data,
  51.         })
  52. }
  53. // Unauthorized 401错误, 常用于权限校验失败
  54. func Unauthorized(c *gin.Context, data any, opts *ResponseOption) {
  55.         if opts == nil {
  56.                 opts = &ResponseOption{
  57.                         Msg: "unauthorized",
  58.                 }
  59.         }
  60.         c.JSON(http.StatusUnauthorized, baseResponse{
  61.                 Code: http.StatusUnauthorized,
  62.                 Msg:  opts.Msg,
  63.                 Data: data,
  64.         })
  65. }
  66. // Forbidden 403错误, 常用于权限不足
  67. func Forbidden(c *gin.Context, data any, opts *ResponseOption) {
  68.         if opts == nil {
  69.                 opts = &ResponseOption{
  70.                         Msg: "forbidden",
  71.                 }
  72.         }
  73.         c.JSON(http.StatusForbidden, baseResponse{
  74.                 Code: http.StatusForbidden,
  75.                 Msg:  opts.Msg,
  76.                 Data: data,
  77.         })
  78. }
  79. // NotFound 404错误, 常用于资源不存在
  80. func NotFound(c *gin.Context, data any, opts *ResponseOption) {
  81.         if opts == nil {
  82.                 opts = &ResponseOption{
  83.                         Msg: "not found",
  84.                 }
  85.         }
  86.         c.JSON(http.StatusNotFound, baseResponse{
  87.                 Code: http.StatusNotFound,
  88.                 Msg:  opts.Msg,
  89.                 Data: data,
  90.         })
  91. }
  92. func MethodNotAllowed(c *gin.Context, data any, opts *ResponseOption) {
  93.         if opts == nil {
  94.                 opts = &ResponseOption{
  95.                         Msg: "method not allowed",
  96.                 }
  97.         }
  98.         c.JSON(http.StatusMethodNotAllowed, baseResponse{
  99.                 Code: http.StatusMethodNotAllowed,
  100.                 Msg:  opts.Msg,
  101.                 Data: data,
  102.         })
  103. }
  104. // UnprocessableEntity 422错误, 常用于客户端参数导致业务逻辑处理异常
  105. func UnprocessableEntity(c *gin.Context, data any, opts *ResponseOption) {
  106.         if opts == nil {
  107.                 opts = &ResponseOption{
  108.                         Msg: "unprocessable entity",
  109.                 }
  110.         }
  111.         c.JSON(http.StatusUnprocessableEntity, baseResponse{
  112.                 Code: http.StatusUnprocessableEntity,
  113.                 Msg:  opts.Msg,
  114.                 Data: data,
  115.         })
  116. }
  117. // InternalServerError 500错误, 常用于服务器内部错误
  118. func InternalServerError(c *gin.Context, data any, opts *ResponseOption) {
  119.         if opts == nil {
  120.                 opts = &ResponseOption{
  121.                         Msg: "internal server error",
  122.                 }
  123.         }
  124.         c.JSON(http.StatusInternalServerError, baseResponse{
  125.                 Code: http.StatusInternalServerError,
  126.                 Msg:  opts.Msg,
  127.                 Data: data,
  128.         })
  129. }
复制代码
程序入口


  • main.go
  1. package main
  2. import (
  3.         "log/slog"
  4.         "net"
  5.         "net/http/httputil"
  6.         "os"
  7.         "runtime/debug"
  8.         "strings"
  9.         "tmpgo/pkg/response"
  10.         "github.com/gin-gonic/gin"
  11. )
  12. func main() {
  13.         gin.SetMode(gin.ReleaseMode) // 生产环境设为 ReleaseMode
  14.         r := gin.New()  // 不要用 gin.Default()
  15.         // 添加 Logger 和 Recovery 中间件
  16.         r.Use(gin.Logger())
  17.         r.Use(CustomRecovery()) // 使用自定义异常恢复中间件
  18.         // 注册路由
  19.         r.GET("/a1", func(c *gin.Context) {
  20.                 response.Success(c, nil, nil)
  21.         })
  22.         r.GET("/a2", func(c *gin.Context) {
  23.                 var respData = struct {
  24.                         Name string
  25.                 }{
  26.                         Name: "hello",
  27.                 }
  28.                 response.Success(c, respData, &response.ResponseOption{
  29.                         Msg: "how a successful response",
  30.                 })
  31.         })
  32.         r.GET("/b1", func(c *gin.Context) {
  33.                 response.UnprocessableEntity(c, nil, nil)
  34.         })
  35.         r.GET("/b2", func(c *gin.Context) {
  36.                 panic("panic something")
  37.         })
  38.         // 设置自定义 404 处理
  39.         r.NoRoute(func(c *gin.Context) {
  40.                 response.NotFound(c, nil, nil)
  41.         })
  42.         // 设置自定义 405 处理(方法不允许)
  43.         r.NoMethod(func(c *gin.Context) {
  44.                 response.MethodNotAllowed(c, nil, nil)
  45.         })
  46.         r.Run("127.0.0.1:10000")
  47. }
  48. // 在正式项目中,可以统一放到中间件的模块中
  49. // CustomRecovery 自定义异常恢复中间件
  50. func CustomRecovery() gin.HandlerFunc {
  51.         return func(c *gin.Context) {
  52.                 defer func() {
  53.                         if err := recover(); err != nil {
  54.                                 var brokenPipe bool
  55.                                 // 检测是否是连接中断
  56.                                 if ne, ok := err.(*net.OpError); ok {
  57.                                         if se, ok := ne.Err.(*os.SyscallError); ok {
  58.                                                 if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
  59.                                                         strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
  60.                                                         brokenPipe = true
  61.                                                 }
  62.                                         }
  63.                                 }
  64.                                 // 获取堆栈信息
  65.                                 stack := string(debug.Stack())
  66.                                 // 获取原始请求内容(可选,方便排查是哪个参数导致的崩溃)
  67.                                 httpRequest, _ := httputil.DumpRequest(c.Request, false)
  68.                                 if brokenPipe {
  69.                                         c.Abort()
  70.                                         response.InternalServerError(c, nil, &response.ResponseOption{Msg: "network abort"})
  71.                                         return
  72.                                 }
  73.                                 slog.Error("exception catched", "error", err, "stack", stack, "request", string(httpRequest))
  74.                                 // c.AbortWithStatusJSON()
  75.                                 c.Abort()
  76.                                 response.InternalServerError(c, nil, &response.ResponseOption{Msg: "server internal error"})
  77.                         }
  78.                 }()
  79.                 c.Next()
  80.         }
  81. }
复制代码
调用示例
  1. $ curl -X GET http://127.0.0.1:10000/a1
  2. {"code":200,"msg":"success","data":null}
  3. $ curl http://127.0.0.1:10000/a1
  4. {"code":200,"msg":"success","data":null}
  5. $ curl http://127.0.0.1:10000/a2
  6. {"code":200,"msg":"how a successful response","data":{"Name":"hello"}}
  7. $ curl http://127.0.0.1:10000/a3
  8. {"code":404,"msg":"not found","data":null}
  9. $ curl http://127.0.0.1:10000/b1
  10. {"code":422,"msg":"unprocessable entity","data":null}
  11. $ curl http://127.0.0.1:10000/b2
  12. {"code":500,"msg":"server internal error","data":null}
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册