在现代Web应用开发中,统一和规范化的API响应格式对于前后端协作至关重要。今天,我们来探讨如何在Gin框架中设计一套既实用又易于维护的响应格式规范。
为什么需要统一的响应格式?
首先,让我们思考一个问题:为什么要统一API响应格式?
- 前后端协作效率:一致的响应格式让前端开发者能以统一的方式处理服务端响应
- 错误处理简化:标准化的错误码和消息便于统一处理各种异常情况
- 接口文档维护:规范化响应减少文档编写工作量
- 客户端适配:移动端或其他客户端可以复用相同的响应解析逻辑
设计统一的响应结构
让我们从最基础的响应结构开始。在本文的示例项目中,我采用了如下的响应结构:- type baseResponse struct {
- Code int `json:"code"`
- Msg string `json:"msg"`
- Data any `json:"data"`
- }
复制代码 这个结构包含了三个基本元素:
- Code: HTTP状态码或业务状态码,表示请求的执行结果
- Msg: 人类可读的消息,描述请求的执行状态。(吐槽一下,见过各种项目,有的用"message", 有的用""messages", 调用方稍不留神就写错了,所以干脆用缩写)
- Data: 实际的业务数据,根据不同接口返回不同内容
有的业务还需要返回timestamp或者trace_id之类的内容,可以根据实际需求来修改。
实现响应处理工具类
有了基础结构后,我们可以构建一个响应处理工具类。在我的项目中,pkg/response/response.go 文件实现了多种常用的响应方法:- // Success 返回200状态码, 默认返回成功
- func Success(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "success",
- }
- }
- c.JSON(http.StatusOK, baseResponse{
- Code: http.StatusOK,
- Msg: opts.Msg,
- Data: data,
- })
- }
复制代码 通过这种方式,我们可以针对不同的HTTP状态码提供专门的响应方法:
- Success: 正常业务响应
- BadRequest: 参数校验失败
- Unauthorized: 权限校验失败
- NotFound: 资源不存在
- InternalServerError: 服务器内部错误
处理异常情况
仅仅处理正常的业务响应是不够的,我们还需要统一拦截异常进行处理,否则异常和未注册路由都不会返回我们需要的格式。这里我用一个自定义的异常恢复中间件做异常捕获:- func CustomRecovery() gin.HandlerFunc {
- return func(c *gin.Context) {
- defer func() {
- if err := recover(); err != nil {
- var brokenPipe bool
- // 检测是否是连接中断
- if ne, ok := err.(*net.OpError); ok {
- if se, ok := ne.Err.(*os.SyscallError); ok {
- if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
- strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
- brokenPipe = true
- }
- }
- }
- // 获取堆栈信息
- stack := string(debug.Stack())
- if brokenPipe {
- c.Abort()
- response.InternalServerError(c, nil, &response.ResponseOption{Msg: "network abort"})
- return
- }
- slog.Error("exception catched", "error", err, "stack", stack)
- c.Abort()
- response.InternalServerError(c, nil, &response.ResponseOption{Msg: "server internal error"})
- }
- }()
- c.Next()
- }
- }
复制代码 这个中间件有几个亮点:
- 连接中断处理:特别处理了 "broken pipe" 和 "connection reset by peer" 错误,避免客户端提前断开连接时产生冗余错误日志
- 错误信息记录:记录错误详情和堆栈信息,便于问题排查
- 统一错误响应:所有异常都以统一格式返回给客户端
路由未找到处理
除了异常处理,我们还需要处理请求路由不存在的情况:- r.NoRoute(func(c *gin.Context) {
- response.NotFound(c, nil, &response.ResponseOption{
- Msg: "接口不存在",
- })
- })
复制代码 这样,当用户请求不存在的接口时,也会收到格式统一的响应。
使用示例
在实际使用中,我们的控制器代码变得简洁明了:- r.GET("/a1", func(c *gin.Context) {
- response.Success(c, nil, nil)
- })
- r.GET("/a2", func(c *gin.Context) {
- var respData = struct {
- Name string
- }{
- Name: "hello",
- }
- response.Success(c, respData, &response.ResponseOption{
- Msg: "how a successful response",
- })
- })
复制代码 无论是在成功响应还是错误响应中,客户端收到的都是相同格式的JSON数据,极大地提升了开发体验。
补充-完整代码示例
项目结构:- ├── go.mod
- ├── go.sum
- ├── main.go
- └── pkg
- └── response
- └── response.go
复制代码 响应类
- package response
- import (
- "net/http"
- "github.com/gin-gonic/gin"
- )
- type baseResponse struct {
- Code int `json:"code"`
- Msg string `json:"msg"`
- Data any `json:"data"`
- }
- type ResponseOption struct {
- Msg string `json:"msg"`
- }
- // Success 返回200状态码, 默认返回成功
- func Success(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "success",
- }
- }
- c.JSON(http.StatusOK, baseResponse{
- Code: http.StatusOK,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // SuccessCreated 返回201状态码, 表示创建成功。常用于新增数据
- func SuccessCreated(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "success",
- }
- }
- c.JSON(http.StatusCreated, baseResponse{
- Code: http.StatusCreated,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // BadRequest 返回400错误, 常用于参数校验失败
- func BadRequest(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "bad request",
- }
- }
- c.JSON(http.StatusBadRequest, baseResponse{
- Code: http.StatusBadRequest,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // Unauthorized 401错误, 常用于权限校验失败
- func Unauthorized(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "unauthorized",
- }
- }
- c.JSON(http.StatusUnauthorized, baseResponse{
- Code: http.StatusUnauthorized,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // Forbidden 403错误, 常用于权限不足
- func Forbidden(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "forbidden",
- }
- }
- c.JSON(http.StatusForbidden, baseResponse{
- Code: http.StatusForbidden,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // NotFound 404错误, 常用于资源不存在
- func NotFound(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "not found",
- }
- }
- c.JSON(http.StatusNotFound, baseResponse{
- Code: http.StatusNotFound,
- Msg: opts.Msg,
- Data: data,
- })
- }
- func MethodNotAllowed(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "method not allowed",
- }
- }
- c.JSON(http.StatusMethodNotAllowed, baseResponse{
- Code: http.StatusMethodNotAllowed,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // UnprocessableEntity 422错误, 常用于客户端参数导致业务逻辑处理异常
- func UnprocessableEntity(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "unprocessable entity",
- }
- }
- c.JSON(http.StatusUnprocessableEntity, baseResponse{
- Code: http.StatusUnprocessableEntity,
- Msg: opts.Msg,
- Data: data,
- })
- }
- // InternalServerError 500错误, 常用于服务器内部错误
- func InternalServerError(c *gin.Context, data any, opts *ResponseOption) {
- if opts == nil {
- opts = &ResponseOption{
- Msg: "internal server error",
- }
- }
- c.JSON(http.StatusInternalServerError, baseResponse{
- Code: http.StatusInternalServerError,
- Msg: opts.Msg,
- Data: data,
- })
- }
复制代码 程序入口
- package main
- import (
- "log/slog"
- "net"
- "net/http/httputil"
- "os"
- "runtime/debug"
- "strings"
- "tmpgo/pkg/response"
- "github.com/gin-gonic/gin"
- )
- func main() {
- gin.SetMode(gin.ReleaseMode) // 生产环境设为 ReleaseMode
- r := gin.New() // 不要用 gin.Default()
- // 添加 Logger 和 Recovery 中间件
- r.Use(gin.Logger())
- r.Use(CustomRecovery()) // 使用自定义异常恢复中间件
- // 注册路由
- r.GET("/a1", func(c *gin.Context) {
- response.Success(c, nil, nil)
- })
- r.GET("/a2", func(c *gin.Context) {
- var respData = struct {
- Name string
- }{
- Name: "hello",
- }
- response.Success(c, respData, &response.ResponseOption{
- Msg: "how a successful response",
- })
- })
- r.GET("/b1", func(c *gin.Context) {
- response.UnprocessableEntity(c, nil, nil)
- })
- r.GET("/b2", func(c *gin.Context) {
- panic("panic something")
- })
- // 设置自定义 404 处理
- r.NoRoute(func(c *gin.Context) {
- response.NotFound(c, nil, nil)
- })
- // 设置自定义 405 处理(方法不允许)
- r.NoMethod(func(c *gin.Context) {
- response.MethodNotAllowed(c, nil, nil)
- })
- r.Run("127.0.0.1:10000")
- }
- // 在正式项目中,可以统一放到中间件的模块中
- // CustomRecovery 自定义异常恢复中间件
- func CustomRecovery() gin.HandlerFunc {
- return func(c *gin.Context) {
- defer func() {
- if err := recover(); err != nil {
- var brokenPipe bool
- // 检测是否是连接中断
- if ne, ok := err.(*net.OpError); ok {
- if se, ok := ne.Err.(*os.SyscallError); ok {
- if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
- strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
- brokenPipe = true
- }
- }
- }
- // 获取堆栈信息
- stack := string(debug.Stack())
- // 获取原始请求内容(可选,方便排查是哪个参数导致的崩溃)
- httpRequest, _ := httputil.DumpRequest(c.Request, false)
- if brokenPipe {
- c.Abort()
- response.InternalServerError(c, nil, &response.ResponseOption{Msg: "network abort"})
- return
- }
- slog.Error("exception catched", "error", err, "stack", stack, "request", string(httpRequest))
- // c.AbortWithStatusJSON()
- c.Abort()
- response.InternalServerError(c, nil, &response.ResponseOption{Msg: "server internal error"})
- }
- }()
- c.Next()
- }
- }
复制代码 调用示例
- $ curl -X GET http://127.0.0.1:10000/a1
- {"code":200,"msg":"success","data":null}
- $ curl http://127.0.0.1:10000/a1
- {"code":200,"msg":"success","data":null}
- $ curl http://127.0.0.1:10000/a2
- {"code":200,"msg":"how a successful response","data":{"Name":"hello"}}
- $ curl http://127.0.0.1:10000/a3
- {"code":404,"msg":"not found","data":null}
- $ curl http://127.0.0.1:10000/b1
- {"code":422,"msg":"unprocessable entity","data":null}
- $ curl http://127.0.0.1:10000/b2
- {"code":500,"msg":"server internal error","data":null}
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |