Skip to content

第13章:统一响应格式与错误处理 ​

本章目标 ​

学完本章后,你将能够:

  1. 设计统一的 API 响应结构 {code, message, data}
  2. 封装可复用的响应工具函数
  3. 区分 HTTP 状态码和业务状态码
  4. 使用 c.Error() / c.Abort() 传递和处理错误

前置知识 ​

  • 需要先学习:第12章(响应渲染)、第11章(参数验证)

13.1 为什么需要统一响应格式 ​

go
// ❌ 每个接口返回格式不一样
{"msg":"ok","user":{"name":"张三"}}                    // 成功1
{"error":"用户不存在"}                                  // 失败1
{"success":true,"data":{"name":"张三"},"message":"成功"} // 成功2
// 前端同学疯了...

// ✅ 统一格式
{"code":0,"message":"success","data":{"name":"张三"}}  // 成功
{"code":1001,"message":"用户不存在","data":null}       // 失败

13.2 标准响应结构 ​

go
// Response 统一响应结构
type Response struct {
	Code    int         `json:"code"`    // 业务状态码(0=成功,非0=失败)
	Message string      `json:"message"` // 提示信息
	Data    interface{} `json:"data"`    // 响应数据
}

13.3 封装响应工具函数 ​

go
package response

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

type Response struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data"`
}

// Success 成功响应
func Success(c *gin.Context, data interface{}) {
	c.JSON(http.StatusOK, Response{
		Code:    0,
		Message: "success",
		Data:    data,
	})
}

// Created 创建成功
func Created(c *gin.Context, data interface{}) {
	c.JSON(http.StatusCreated, Response{
		Code:    0,
		Message: "created",
		Data:    data,
	})
}

// Error 错误响应
func Error(c *gin.Context, httpStatus int, code int, message string) {
	c.JSON(httpStatus, Response{
		Code:    code,
		Message: message,
		Data:    nil,
	})
}

// BadRequest 400 错误
func BadRequest(c *gin.Context, message string) {
	Error(c, http.StatusBadRequest, 400, message)
}

// NotFound 404 错误
func NotFound(c *gin.Context, message string) {
	Error(c, http.StatusNotFound, 404, message)
}

// InternalError 500 错误
func InternalError(c *gin.Context, message string) {
	Error(c, http.StatusInternalServerError, 500, message)
}

// ValidationError 参数验证失败
func ValidationError(c *gin.Context, message string) {
	Error(c, http.StatusUnprocessableEntity, 422, message)
}

使用示例 ​

go
package handlers

import (
	"gin-project/response"
	"github.com/gin-gonic/gin"
)

func GetUser(c *gin.Context) {
	user := User{ID: 1, Name: "张三", Age: 25}
	response.Success(c, user)
	// {"code":0,"message":"success","data":{"id":1,"name":"张三","age":25}}
}

func CreateUser(c *gin.Context) {
	var req CreateUserRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.ValidationError(c, "参数验证失败: "+err.Error())
		return
	}
	response.Created(c, gin.H{"id": 1})
	// {"code":0,"message":"created","data":{"id":1}}
}

func DeleteUser(c *gin.Context) {
	id := c.Param("id")
	// 用户不存在
	response.NotFound(c, "用户 "+id+" 不存在")
	// {"code":404,"message":"用户 999 不存在","data":null}
}

13.4 业务状态码设计 ​

范围含义示例
0成功0
1000-1999用户相关1001=不存在, 1002=密码错误, 1003=已被注册
2000-2999订单相关2001=库存不足, 2002=已取消
4000-4999通用客户端错误4001=参数缺失, 4002=格式错误
5000-5999服务端错误5001=数据库错误, 5002=第三方接口失败
9999未知错误9999

13.5 Gin 内置错误处理 ​

c.Error():沿中间件链传递错误 ​

go
// 中间件中记录错误
func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.GetHeader("Authorization")
		if token == "" {
			c.Error(fmt.Errorf("缺少认证令牌"))
			c.AbortWithStatusJSON(401, gin.H{"msg": "未认证"})
			return
		}
		c.Next()
	}
}

// 在 Logger 或其他中间件中收集错误
r.Use(func(c *gin.Context) {
	c.Next()
	// 处理完成后查看是否有错误
	for _, err := range c.Errors {
		fmt.Println("请求错误:", err.Err)
	}
})

Abort 系列方法 ​

go
c.Abort()                           // 中止后续处理,不返回响应
c.AbortWithStatus(401)              // 中止 + 只返回状态码
c.AbortWithStatusJSON(401, data)    // 中止 + 返回 JSON(最常用)
c.AbortWithError(500, err)          // 中止 + 添加错误 + 返回状态码

常见错误 ​

错误1:HTTP 状态码和业务状态码混淆 ​

go
// ❌ 业务错误却用 500
// 用户不存在 → 这不是服务器错误!
c.JSON(500, Response{Code: 1001, Message: "用户不存在"})

// ✅ 用户不存在 → 404,业务码区分细节
c.JSON(404, Response{Code: 1001, Message: "用户不存在"})

错误2:Abort 后忘记 return ​

go
func handler(c *gin.Context) {
	if err != nil {
		c.AbortWithStatusJSON(400, gin.H{"error": "参数错误"})
		// ❌ 忘记 return!后续代码继续执行
	}
	// 还会执行这里!
	c.JSON(200, gin.H{"data": "xxx"})
	// 两次写响应 → 日志警告
}

// ✅ Abort 后必须 return
func handler(c *gin.Context) {
	if err != nil {
		c.AbortWithStatusJSON(400, gin.H{"error": "参数错误"})
		return  // 必须!
	}
	c.JSON(200, gin.H{"data": "xxx"})
}

本章小结 ​

  • 统一响应格式:{code, message, data}
  • code=0 表示成功,非0表示失败,用不同范围区分错误类型
  • 封装 Success/Error/BadRequest/NotFound/InternalError 等函数
  • HTTP 状态码和业务状态码各司其职
  • c.Abort() + return 是一对好搭档

练习题 ​

  1. 封装一个 response 包,包含 Success/Error/BadRequest/NotFound/InternalError 函数。
  2. 设计一套业务状态码(用户、订单、通用),写入代码中。
  3. 用封装好的 response 包重写之前练习中的接口。
  4. (思考题)为什么 HTTP 状态码和业务状态码要分开?只用 HTTP 状态码不够吗?

把零散知识,整理成可以反复走的路。