Code字段应填HTTP状态码,业务码需另设BizCode字段解耦;Response结构体须规范导出、JSON tag、omitempty及Timestamp类型,封装函数末尾必须return。

Code 字段到底该填 HTTP 状态码还是业务码
填 HTTP 状态码。别用 Code: 1001 配 http.StatusOK,Nginx 和 CDN 会按 200 缓存错误响应,前端也拿不到真实语义。业务码要加新字段,比如 BizCode int `json:"biz_code,omitempty"`,和 transport 层解耦。
常见错误现象:返回 {"code":4001,"msg":"参数错误"} 却设了 200 OK,导致 Axios 默认不走 error 拦截器,前端以为成功了。
- 所有
c.JSON()调用必须显式传入 HTTP 状态码,不能硬编码200 -
Code字段只用于前端 switch 判断展示逻辑(如 toast 提示),不参与服务端路由或缓存决策 - 如果已有历史接口用业务码占了
Code,新增BizCode字段并逐步迁移,别强行改旧字段语义
Response 结构体字段导出和 JSON tag 容易漏哪几处
字段首字母小写、json tag 拼错、漏写 omitempty(对 Data 很关键)、Timestamp 用了 time.Time 类型——这四点一漏就 panic 或返回空字段。
正确写法必须是:
立即学习“go语言免费学习笔记(深入)”;
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Timestamp int64 `json:"timestamp"`
}
-
Data用interface{}是为了泛型兼容,但 handler 里传的必须是可序列化值,比如&User{}或map[string]string{"id": "1"},不能传func()或未导出字段的 struct -
Timestamp必须是int64,用time.Now().UnixMilli(),不是Format("2006-01-02")——后者触发 GC 且带时区歧义 - 别给
Message加指针(*string),空提示用"",不是nil
Gin 中 Success / Error 函数为什么总 panic
因为没 return。Gin 的 c.JSON() 不会自动终止执行,后续代码继续跑,第二次调用 c.JSON() 或 c.String() 就触发 http: multiple response.WriteHeader calls。
正确封装示例:
func Success(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Response{
Code: http.StatusOK,
Message: "success",
Data: data,
Timestamp: time.Now().UnixMilli(),
})
return // 这行不能少
}
func Error(c *gin.Context, statusCode int, message string) {
c.JSON(statusCode, Response{
Code: statusCode,
Message: message,
Data: nil,
Timestamp: time.Now().UnixMilli(),
})
return
}
- 所有封装函数末尾必须加
return,不能依赖 defer 或上层逻辑拦截 - 别在封装里做
if err != nil { Error() },错误分支由 handler 自己控制,封装函数只负责“写一次响应” - 禁止混用
c.Abort()和c.JSON(),Abort 只影响中间件链,不发响应;要发响应必须调用c.JSON()或类似方法
为什么不该用中间件自动包装响应
中间件无法感知 handler 是否已调用 c.JSON()、是否已 http.Error()、是否 panic 后被 recovery 捕获——它一插手,就可能重复写 header、覆盖状态码、或把 500 错误包成 200 {"code":500}。
真实踩坑场景:
- handler 里调了
c.AbortWithStatusJSON(400, ...),中间件又套一层,变成嵌套 JSON - 数据库查询报
sql: no rows in result set,中间件捕获 panic 后统一返回{"code":500},掩盖了业务语义(其实是“用户不存在”) - 某个 handler 忘记
return,中间件二次调用c.JSON(),直接 panic
真正可控的做法:每个 handler 显式构造 Response + c.JSON(),路径唯一、分支清晰、日志可追溯。复杂点在于要手动写每条路径,但换来的是稳定和可测。


















