真正能落地的统一封装必须从结构体定义、函数调用习惯、错误路径三处卡死边界:code为http状态码,message为非nil字符串,data为可序列化类型,timestamp用unixmilli(),分页信息独立于data字段,success/ error函数须显式return且不混用abort与json。

直接在每个 handler 里写 c.JSON(200, map[string]interface{}{"code": 0, "msg": "ok", "data": user}),上线后你就会在日志里看到 17 种不同结构的响应——前端根本没法写统一解析逻辑。真正能落地的统一封装,必须从结构体定义、函数调用习惯、错误路径三处卡死边界,而不是加个中间件自动包。
Response 结构体字段必须严格按语义拆分
别抄网上“万能 struct”,字段一多就失控。只保留四个字段,少一个难维护,多一个易误用:
-
Code:必须是 HTTP 状态码(http.StatusOK、http.StatusBadRequest),不是业务码。Nginx 缓存、CDN 判断、浏览器重试全靠它 -
Message:纯string类型,非指针;空值用"",不是nil;生产环境只传用户可见提示,别塞"pq: duplicate key" -
Data:类型为interface{},但实际只允许传可序列化类型(User{}、[]Post、map[string]string),且所有字段首字母大写;禁止传func、chan、未导出字段的 struct -
Timestamp:用time.Now().UnixMilli(),不是time.Time或Format();前端直接用Date.now()对齐,避免时区/解析歧义
分页字段(total、page、page_size)绝对不塞进 Data——它是传输元信息,不是业务数据。要么单独提一层字段,要么由前端按 header 或约定路径解析。
Gin 中 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 字段
前端同学会疯。当 Data 是 []User{} 时,total 和 page 要额外取;当 Data 是单个 User{} 时,这些字段又变成冗余或报错。TypeScript 接口得写成 Data: T | Paginated<t></t>,徒增复杂度。
- 列表接口返回结构应为:
{ "code": 200, "message": "ok", "data": [...], "pagination": { "total": 100, "page": 1, "page_size": 20 } } - 非列表接口不带
pagination字段,避免前端每次都要做类型断言或判空 - 如果坚持扁平化,至少用独立顶层字段(如
total、page),而非嵌套在Data里
业务码和 HTTP 状态码必须解耦。真要加业务码?加个 BusinessCode int `json:"biz_code,omitempty"` 字段,别动 Code。
中间件自动包装响应是危险操作
中间件无法判断 handler 是否已写入响应、是否已调用 http.Error、甚至是否 panic 了——一包装就可能重复写 header 或 panic。
- 推荐在每个 handler 末尾显式构造响应:
c.JSON(http.StatusOK, Response{...}) - 错误路径也走同一结构体,比如
Response{Code: 400, Message: "invalid id"},而不是混用http.Error - 若要用中间件,仅用于捕获 panic(如 Gin 的
Recovery),并转为标准Error响应;绝不用于“自动包成功返回”
最易被忽略的是 Timestamp 类型和精度——用 UnixMilli() 是硬性要求,Unix() 会丢毫秒级精度,导致前后端时间对不齐;而 Format() 会触发 GC 且引入时区歧义。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











