gin 不自动根据 accept 头切换响应格式,必须显式调用 c.json() 或 c.xml();推荐初期用 gin.h 快速返回 json,接口增多时应统一使用结构体封装响应,并注意字段导出、类型安全与多次写响应的 panic 风险。

为什么不能靠 Accept 头自动选 JSON/XML
Gin 不会根据请求头里的 Accept: application/xml 自动切响应格式——它压根不读这个字段。你写 c.JSON() 就只发 JSON,c.XML() 就只发 XML,中间没任何“智能路由”逻辑。想靠 header 切格式,得自己解析 c.GetHeader("Accept") 再分支调用,但实际项目里几乎没人这么干:前端通常明确约定格式(比如全部走 JSON),强行加一层判断反而增加维护成本、掩盖真实需求。
gin.H 是最简 JSON 响应的合理起点
直接用 gin.H{"code": 0, "message": "ok", "data": user} 配合 c.JSON(200, ...) 是开发初期最快落地的方式。它省去定义结构体的样板代码,字段名即 JSON key,支持嵌套 map 和 slice,也兼容 nil 值序列化。但要注意:
- 字段名必须首字母大写,否则
encoding/json会忽略(Go 导出规则) - 别往里塞函数、channel、未导出 struct 字段,否则 panic 报
json: unsupported type - 如果字段值是时间类型,
gin.H会按默认格式转成字符串,不如用具体 struct +json:"time,string"控制精度
统一响应结构体该什么时候引入
当接口超过 5 个、团队开始对接前端或第三方系统时,就该收口了。此时再用 gin.H 容易漏字段、错 code 约定、timestamp 格式不一致。推荐结构体长这样:
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Timestamp int64 `json:"timestamp"`
}
关键点:
-
Code用int,不是字符串——前端 switch 更稳;0 表示成功,非 0 是业务错误码 -
Data保持interface{},但实际传参时优先用具体类型(如*User或map[string]string),避免运行时 panic -
Timestamp用time.Now().UnixMilli(),别用Format()——减少 GC 和字符串拼接开销 - 别把数据库 model 直接塞进
Data,容易泄露密码字段或触发序列化 panic
c.JSON() 调用前必须 return
封装好 Success(c *gin.Context, data interface{}) 后,每次调用末尾必须加 return。否则后续代码继续执行,可能再次调用 c.JSON() 或 c.String(),触发 http: multiple response.WriteHeader calls panic。这不是 Gin 的 bug,是 HTTP 协议限制:一个请求只能写一次状态码和响应头。
最容易被忽略的是中间件和 handler 的协作关系——如果你用了全局 recovery 中间件,它只捕获 panic,不会拦截你主动写的 Fail(c, ...)。所以 Fail 函数里也要有 return,且状态码(如 401)要和 Code 字段解耦:HTTP 状态码控制连接层行为,Code 字段供业务逻辑判断。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










