分页参数必须按版本拆分结构体,v1用page/pagesize带min/max校验,v2改用cursor/limit并禁用page;绑定需独立shouldbindquery,查询与响应结构、中间件、监控均须版本隔离。

版本迭代时分页参数变更不是“加个字段”就能解决的事——page 和 page_size 的语义、校验规则、默认值甚至是否保留,都可能在 v2 中被调整或废弃。硬编码兼容逻辑会迅速让 handler 变成条件判断泥潭。
分页参数结构体必须按版本拆开定义
别用一个 Pagination 结构体混着绑定 v1/v2 请求。v1 可能要求 page 必填且最小为 1,v2 改成可选、默认为 0(表示游标起始),还新增了 cursor 字段。
- v1 的结构体只包含
Page和PageSize,binding标签写死min=1和max=50 - v2 的结构体去掉
Page,换成Cursor string和Limit int,binding允许空字符串并限制max=100 - 字段名不同(如
page_size→limit)意味着前端 URL 参数名也变,不能靠 tag 映射掩盖差异
Gin 中绑定 Query 参数必须走独立结构体 + ShouldBindQuery
别在 v2 handler 里复用 v1 的 c.ShouldBindQuery(&v1Params),Gin 不会自动跳过不存在字段,而是报 Key: 'v1Params.PageSize' Error:Field validation for 'PageSize' failed on the 'required' tag 这类错误。
- 每个版本的 handler 必须声明对应版本的参数结构体变量
- 统一用
c.ShouldBindQuery(¶ms),它会忽略 URL 中多出的参数,也不会因缺失字段 panic(前提是 binding 规则写对) - 若 v2 完全弃用
page,但旧客户端仍传该参数,ShouldBindQuery会静默跳过——这比手动c.Query("page")再判空更安全
分页逻辑不能复用,尤其涉及 offset 计算和游标生成
v1 的 offset = (page - 1) * page_size 在 v2 游标分页中毫无意义;反过来,v2 的 WHERE id > ? ORDER BY id ASC LIMIT ? 查询也不能套到 v1 上。
- 数据库查询构建必须隔离:v1 走 GORM 的
Offset().Limit(),v2 走Where("id > ?", cursor).Order("id ASC").Limit() - 总数统计逻辑也要分开:v1 需
Count(),v2 游标分页通常不返回总页数,只返回has_next布尔值 - 响应结构体同样要分版本:
PaginatedResponseV1含Total和Pages,PaginatedResponseV2含NextCursor和HasNext
最容易被忽略的点:中间件和日志里的版本感知
如果你在全局日志中间件里打印了 c.Query("page"),v2 请求带 cursor 时这条日志就失效了;如果分页限流中间件只检查 page_size,v2 的 limit 就绕过了限制。
- 所有中间件必须从 context 或路由信息中明确获取当前 API 版本号,而不是解析 query 参数猜
- 推荐在路由 Group 层把版本注入 context:
v2.Use(versionMiddleware("v2")),后续中间件直接取c.MustGet("version").(string) - 监控指标(如“v2 分页请求平均延迟”)必须按版本打标,否则性能退化会被平均掉
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











