Skip to content

Gin 实战 9:统一响应与异常

CRUD 接口已经能跑,但 Handler 里到处 c.JSON(400/500, ...) 会让响应格式越来越乱。本章统一成功响应、分页响应和错误出口。

1. 定义响应结构

创建 internal/common/response/response.go

go
package response

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

type Result struct {
    // code 给前端判断业务状态,成功固定为 00000。
    Code string `json:"code"`
    // msg 给用户或前端展示。
    Msg  string `json:"msg"`
    // data 承载真正的数据;omitempty 表示为空时不输出。
    Data any    `json:"data,omitempty"`
}

// Ok 统一成功响应。
func Ok(c *gin.Context, data any) {
    c.JSON(http.StatusOK, Result{
        Code: "00000",
        Msg:  "成功",
        Data: data,
    })
}

// Page 统一分页响应,后台管理列表接口会频繁使用。
func Page(c *gin.Context, list any, total int64) {
    Ok(c, gin.H{
        "list":  list,
        "total": total,
    })
}

2. 定义业务错误

创建 pkg/errs/app_error.go

go
package errs

type AppError struct {
    // Code 是业务错误码,例如 A0400 参数错误、B0001 系统异常。
    Code string
    // Msg 是对外返回的错误信息。
    Msg  string
}

// Error 实现 error 接口,这样 AppError 可以作为普通 error 返回。
func (e *AppError) Error() string {
    return e.Msg
}

func BadRequest(msg string) *AppError {
    return &AppError{Code: "A0400", Msg: msg}
}

func System(msg string) *AppError {
    return &AppError{Code: "B0001", Msg: msg}
}

3. 全局错误处理中间件

创建 internal/middleware/error_handler.go

go
package middleware

import (
    "errors"
    "net/http"

    "github.com/gin-gonic/gin"
    "youlai-gin/internal/common/response"
    "youlai-gin/pkg/errs"
)

func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 先执行后续中间件和 Handler。
        // Handler 中调用 c.Error(err) 后,错误会暂存在 c.Errors。
        c.Next()

        // 没有错误就不处理,避免影响正常响应。
        if len(c.Errors) == 0 {
            return
        }

        // 只取最后一个错误作为本次请求的最终错误。
        err := c.Errors.Last().Err
        var appErr *errs.AppError

        // 业务错误按自己的 code/msg 返回。
        if errors.As(err, &appErr) {
            c.JSON(http.StatusOK, response.Result{
                Code: appErr.Code,
                Msg:  appErr.Msg,
            })
            return
        }

        // 未知错误不要把内部细节暴露给前端。
        c.JSON(http.StatusInternalServerError, response.Result{
            Code: "B0001",
            Msg:  "系统异常",
        })
    }
}

4. Handler 改造

go
func (h *Handler) Page(c *gin.Context) {
    var query model.UserQuery
    if err := c.ShouldBindQuery(&query); err != nil {
        // 只把错误交给 Gin,上面的 ErrorHandler 会统一输出 JSON。
        c.Error(errs.BadRequest("参数错误"))
        return
    }

    list, total, err := h.service.Page(query)
    if err != nil {
        // 业务代码不再直接 c.JSON,避免响应格式散落各处。
        c.Error(errs.System("查询用户失败"))
        return
    }

    response.Page(c, list, total)
}

5. main.go 挂载中间件

go
r := gin.New()
r.Use(logger.Middleware())
r.Use(gin.Recovery())
r.Use(middleware.ErrorHandler())

从这里开始,业务代码只返回数据或错误,响应格式交给公共层统一处理。

基于 MIT 许可发布 · 如需部署协助或二开定制,请查看 支持与合作