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())从这里开始,业务代码只返回数据或错误,响应格式交给公共层统一处理。
