分页参数应通过ctx.QueryParam()获取字符串后安全转整型,避免panic;需设默认值、范围限制(如limit≤100)并静默fallback,不可用ctx.Params(属路径参数)。

分页参数怎么从 HTTP 请求里安全提取
直接用 ctx.QueryParam("page") 或 ctx.QueryParam("limit") 拿字符串再转整型是常见做法,但容易 panic——比如传了 page=abc 或空值。Fiber 默认不校验,必须自己兜底。
推荐写个轻量封装函数,统一处理默认值、范围限制和错误返回:
func parsePageParams(ctx *fiber.Ctx) (page, limit int, err error) {
page, err = strconv.Atoi(ctx.QueryParam("page"))
if err != nil || page < 1 {
page = 1
}
limit, err = strconv.Atoi(ctx.QueryParam("limit"))
if err != nil || limit < 1 || limit > 100 {
limit = 20
}
return
}- 硬性限制
limit上限(如 100),防恶意拉取全表 - 不返回 400 给参数错,而是静默 fallback——分页接口对前端更友好
- 别用
ctx.Params,那是路径参数,不是查询参数
如何用 GORM 实现带总数的分页查询
Fiber 本身不处理数据库,实际分页逻辑落在 ORM 层。GORM 的 Limit/Offset 是基础,但总条数需要额外 Count 查询,两次 SQL 很常见——但别手写两个 db.Model().Count() 和 db.Model().Limit().Offset(),容易因条件不一致导致总数和列表错位。
正确做法是复用同一个查询对象:
db := gormDB.Model(&User{})
var total int64
db.Count(&total).Error // 复用条件
<p>var users []User
db.Offset((page - 1) * limit).Limit(limit).Find(&users)- 务必在
Count前加所有 WHERE 条件(如Where("status = ?", 1)),否则总数不准 - 如果用了
Preload关联查询,Count会因 JOIN 产生重复计数,此时得改用子查询或去掉 Preload 单独查总数 - GORM v2 的
Scopes很适合封装分页逻辑,避免重复写 Offset/Limit
响应结构怎么设计才方便前端用
别只返回 data: [...] ,前端分页控件需要知道当前页、总页数、总数。一个扁平的 JSON 结构最稳妥:
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"pages": 5,
"total": 97
}
}构造时注意计算 pages:用 (total + int64(limit) - 1) / int64(limit),别用 float 转换,避免浮点误差。
- 字段名统一用小写+下划线(如
page而非currentPage),减少前后端争议 - 如果 total 很大(千万级),可考虑不返回精确总数,改用
"has_more": true,避免 Count 性能拖垮接口 - 别把分页元信息塞进 HTTP Header(如
X-Total-Count),移动端或某些前端框架读取麻烦
为什么用 Cursor 分页比 Offset 更可靠
当数据高频更新(如订单、动态流),传统 OFFSET 分页会出现漏数据或重复——第 2 页刚查完,新数据插入到第 1 页末尾,下次查第 2 页就偏了。Cursor 分页靠游标(通常是上一页最后一条记录的排序字段值)来定位,天然规避这个问题。
在 Fiber 中实现只需改参数解析和查询逻辑:
lastID := ctx.QueryInt("cursor", 0)
db.Where("id > ?", lastID).Order("id ASC").Limit(limit).Find(&users)- 必须有严格单调递增/递减的索引字段(如
id或created_at),且该字段建了索引 - 不能支持跳页(比如直接跳到第 100 页),只适合“下一页”场景
- Cursor 值要 URL-safe 编码(如 base64),避免传特殊字符被截断
Offset 分页简单直接,Cursor 分页稳定但约束多——选哪个,取决于你的数据变更频率和业务是否允许跳页。上线前一定用并发写入压测验证 Offset 分页的数据一致性。


















