Go服务端API向后兼容需确保旧客户端请求被完整接收:新增字段用指针或omitempty,删字段用json:"-"并注释,类型变更须双字段过渡;路径方法变更须路由层兜底注册;错误码与响应结构须严格保持旧格式。

新增字段必须用指针或omitempty
旧客户端发来的请求里不会带新字段,如果结构体字段是值类型(比如UpdatedAt time.Time),json.Unmarshal会把它设成零值(0001-01-01 00:00:00 +0000 UTC),业务逻辑可能误判为“有效时间”。
- 改成指针:
UpdatedAt *time.Time `json:"updated_at"`,未传时为nil,可明确区分“未提供”和“提供空时间” - 或加
omitempty标签:UpdatedAt time.Time `json:"updated_at,omitempty"`,但注意:零值字段(如0、""、false)也会被跳过,不适合必须区分“0”和“未传”的场景 - 禁止对入参结构体字段加
json:",required"——Go标准库根本不识别这个tag,写了等于没写
删字段或改类型得走过渡期
直接删字段或把Count int改成Count string,旧客户端一发请求就解析失败,返回400 Bad Request或静默截断。这不是“能不能跑”,而是“旧请求是否还能进业务逻辑”。
- 删字段前,先保留字段名,改用
json:"-"并加注释:OldField int `json:"-" // deprecated since v2.1` - 类型变更必须双字段共存:比如同时定义
CountInt int `json:"count"`和CountStr string `json:"count_str,omitempty"`,在Unmarshal后手动做转换 - 所有转换逻辑必须收口在统一入口(比如
BindAndNormalize()函数),别散落在各个handler里
路径和方法变更必须路由层兜底
旧客户端调/v1/users?limit=10,你把接口挪到/v2/users并改成POST+body,不处理的话直接404或405。重定向(301/302)不可行——移动端常禁用,且POST重定向后变GET,body丢失。
- 用
gorilla/mux或chi显式注册旧路径:r.HandleFunc("/v1/users", v2UsersHandler).Methods("GET") - 旧handler里手动解析query参数,映射成新结构体,再调新逻辑;别试图用中间件统一转——每个路径的参数映射规则往往不同
- 响应头加
X-Deprecated: true,并在日志里记录旧路径访问量,方便后续下线决策
错误码和响应结构一个字都不能动
把{"error": "xxx"}改成{"code": 1001, "message": "xxx"},或者把400换成422,旧客户端JSON解析失败或状态码判断错,直接崩溃。
立即学习“go语言免费学习笔记(深入)”;
- 成功响应和错误响应的字段名、嵌套层级、类型都必须1:1保持
- 新增错误码可以,但旧错误码语义不能变;比如
400始终表示“客户端参数错误”,不能某天起变成“权限不足” - 建议用统一响应包装器(如
type Response struct { Success bool `json:"success"` Data interface{} `json:"data"` Message string `json:"message"` }),所有版本共用同一结构体定义


















