response结构体必须严格限定为code、message、data、timestamp四个字段:code为http状态码而非业务码,message为非指针字符串且空值为"",data为可序列化接口类型且字段首字母大写,timestamp用time.now().unixmilli()。

Response 结构体字段必须严格按语义拆分
别抄网上“万能 struct”,字段一多就失控。真正能卡死边界的只有四个字段:Code、Message、Data、Timestamp,少一个难维护,多一个易误用。
-
Code必须是 HTTP 状态码(http.StatusOK、http.StatusBadRequest),不是业务码——Nginx 缓存、CDN 判断、浏览器重试全靠它,混用业务码会导致缓存策略失效 -
Message是纯string类型,非指针;空值写"",不是nil;生产环境只传用户可见提示,严禁透传"pq: duplicate key"这类原始错误 -
Data类型为interface{},但实际只允许传可序列化类型(User{}、[]Post、map[string]string),且所有字段首字母大写;禁止传func、chan、未导出字段的 struct,否则json.Marshal会 panic -
Timestamp用time.Now().UnixMilli(),不是time.Time或Format();前端直接用Date.now()对齐,避免时区/解析歧义,也减少 GC 压力
分页字段(total、page、page_size)绝对不塞进 Data——它是传输元信息,不是业务数据。要么单独提一层字段,要么由前端按 header 或约定路径解析。
Success / Error 函数末尾必须显式 return
最常见 panic 是封装函数里没 return,导致 c.JSON() 后继续执行,触发 http: multiple response.WriteHeader call。
-
Success(c *gin.Context, data interface{})末尾必须跟return,且只设http.StatusOK;不要在里面写if err != nil { Error() }——错误分支该由 handler 自己控制 -
Error(c *gin.Context, statusCode int, message string)内部固定用c.JSON(statusCode, Response{}),不硬编码200;statusCode必须是真实 HTTP 码(400、401、500) - 禁用
AbortWithError这类混合函数——c.Abort()和c.JSON()是两件事,混用极易漏响应
所有封装函数签名统一为 func(c *gin.Context),不接收 *http.Request 或全局 w,避免 goroutine 竞态。
Data 字段不能用 *interface{},omitempty 要配对使用
Data 字段定义成 *interface{} 是典型陷阱:json.Marshal 对 nil interface{} 能正常序列化为 null,但对 nil *interface{} 会 panic。
- 正确写法是
Data interface{} `json:"data,omitempty"`,空值自动省略,调用方无需纠结传user还是&user - 所有业务返回尽量传具体类型,比如
map[string]interface{}或结构体指针,避免运行时反射 panic - 别在结构体里塞
request_id或pagination这类非通用字段——它们属于业务上下文,应由 handler 自行注入,不是响应体的固有属性
HTTP 状态码和业务码必须分离:Success 固定返回 http.StatusOK,Code 字段恒为 0;Error 接收 bizCode int 和 httpStatus int 两个参数,例如参数校验失败用 Fail(c, 1002, "name is required", http.StatusBadRequest)。
别用中间件自动包装,每个 handler 必须显式调用
中间件无法判断 handler 是否已写入响应、是否已调用 http.Error、甚至是否 panic 了——一包装就可能重复写 header 或 panic。
- 最稳妥方式是每个 handler 显式构造
Response结构体并调用json.NewEncoder(w).Encode(),而不是依赖中间件“打补丁” - 若用了 Gin 的 recovery 中间件,确保它只处理 panic,不干涉你手动写的
Error(c, ...);中间件和主动 abort 不冲突,但和主动c.JSON()混用容易漏响应 - 所有响应路径必须走同一结构体:错误时也用
Response{Code: 400, Message: "xxx"},而不是混用http.Error
真正落地的统一封装,不是加个中间件自动包,而是从结构体定义、handler 调用习惯、错误路径三处卡死边界——一旦松动,两周后日志里就会冒出 17 种不同格式的 {"code":200}。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











