gorm 不参与 api 版本控制,版本隔离必须在路由和 handler 层实现:分页结构、dto、count 查询、游标解析均需按版本独立定义与执行,避免跨版本数据错乱。

直接说结论:GORM 本身不参与 API 版本控制,它只管数据库层;版本控制在路由和 handler 层做,分页逻辑必须按版本隔离实现——否则 v1 客户端可能拿到 v2 的分页总数或字段结构,导致前端解析失败或分页错乱。
为什么 GORM 不能“自动适配”API 版本
GORM 操作的是 domain model(比如 User struct),而 API 版本暴露的是 DTO(比如 UserV1、UserV2)。这两者语义不同:User 是数据库映射,字段稳定;UserV1 是契约,字段增减、可选性、JSON tag 都可能变化。GORM 不知道也不该知道你当前服务的是 v1 还是 v2。
- 如果你在 handler 里直接
db.Find(&users)后c.JSON(200, users),那返回的就是 domain model,v1/v2 客户端收到的字段完全一样——这违反了版本隔离原则 - 如果你用同一个
PaginatedResponse{Data: users, Total: total}结构体复用所有版本,Data字段类型没泛型约束,JSON 序列化时无法校验是否为对应版本 DTO - GORM 的
Count()查询结果是纯数字,但总条数是否对齐版本?比如 v2 加了新过滤条件(status = 'active'),而 v1 仍查全部,这时共用一个 count 查询就错了
分页结构体必须按版本声明
别图省事写一个通用 PaginatedResponse 然后塞不同版本的数据。Go 没有泛型反射,运行时无法保证 Data 字段类型正确。每个版本应定义专属响应结构:
-
PaginatedUserV1Response包含Data []UserV1和Total int64 -
PaginatedUserV2Response包含Data []UserV2,哪怕当前字段一致也必须分开 - 分页参数(
page、limit)可复用PaginationRequest,但绑定后必须在校验通过后才进入对应版本的 handler 分支
示例(v1 handler 中):
var req PaginationRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(400, ErrorResponse{Message: "invalid page params"})
return
}
var users []UserV1
var total int64
db.Model(&User{}).Where("deleted_at IS NULL").Count(&total)
db.Order("id ASC").Offset((req.Page - 1) * req.Limit).Limit(req.Limit).Find(&users)
c.JSON(200, PaginatedUserV1Response{
Data: users,
Total: total,
Page: req.Page,
Limit: req.Limit,
})
游标分页比 Limit/Offset 更适合多版本共存
当 v1 和 v2 对同一资源使用不同排序逻辑(比如 v1 按 created_at,v2 按 updated_at)、或不同过滤条件时,Limit/Offset 的 offset 值无法跨版本复用。游标分页把“位置”交给客户端传递(如 ?cursor=12345),天然规避了 offset 计算问题。
- v1 的游标基于
created_at+id,v2 基于updated_at+id,互不影响 - 游标值(如最后一条记录的
id)是业务数据的一部分,不是抽象的“第几页”,不会因其他版本写入而偏移 - 必须为游标字段建联合索引,例如
INDEX idx_created_id (created_at, id),否则性能崩 - 不要在 v1 handler 里复用 v2 的游标解析逻辑——哪怕字段名一样,也要各自实现
parseCursorV1()和parseCursorV2()
中间件里不能偷偷改分页行为
有人想“统一处理分页”,在中间件里解析 page/limit 并塞进 context,再由 handler 取出来用。这看似 DRY,实则埋雷:
- 不同版本对
limit的上限要求可能不同(v1 最大 50,v2 放宽到 200),中间件无法按版本差异化校验 - v2 要求强制带
sort参数,v1 不校验——中间件做不到分支判断 - 如果中间件里执行了
Count(),那这个查询是跑在哪个版本的 WHERE 条件下?没人能保证 - 最稳妥的做法:分页参数解析、校验、count 查询、主查询、DTO 转换,全部收拢在单个版本的 handler 函数内
真正容易被忽略的一点:分页的 Total 不是“全局总数”,而是“该版本当前查询条件下的总数”。v1 和 v2 即使查同一张表,只要 WHERE 不同、JOIN 不同、甚至 JSONB 字段过滤逻辑不同,Total 就必须独立查。别省这一次 SQL。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











