第13章:统一响应格式与错误处理
本章目标
学完本章后,你将能够:
- 设计统一的 API 响应结构
{code, message, data} - 封装可复用的响应工具函数
- 区分 HTTP 状态码和业务状态码
- 使用
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是一对好搭档
练习题
- 封装一个
response包,包含 Success/Error/BadRequest/NotFound/InternalError 函数。 - 设计一套业务状态码(用户、订单、通用),写入代码中。
- 用封装好的 response 包重写之前练习中的接口。
- (思考题)为什么 HTTP 状态码和业务状态码要分开?只用 HTTP 状态码不够吗?