应封装分页参数解析函数,校验page和limit范围并设默认值;OFFSET需用(page-1)*limit计算;响应需含总条数、总页数(向上取整)等字段,并单独执行COUNT查询。

分页参数怎么安全解析并校验
直接用 c.Query("page") 和 c.Query("limit") 拿字符串再转整型,容易 panic 或被注入恶意值。必须做类型转换 + 边界控制。
推荐写个复用函数封装校验逻辑:
func parsePagination(c *fiber.Ctx) (int, int, error) {
page, err := strconv.Atoi(c.Query("page", "1"))
if err != nil || page < 1 {
return 0, 0, fmt.Errorf("invalid page")
}
limit, err := strconv.Atoi(c.Query("limit", "10"))
if err != nil || limit < 1 || limit > 100 {
return 0, 0, fmt.Errorf("invalid limit, must be 1-100")
}
return page, limit, nil
}- 默认值设为
"1"和"10",避免空参导致 0 值计算错误 -
limit强制上限 100,防 SQL 注入或 OOM(比如传?limit=9999999) - 别在 handler 里裸写
strconv.Atoi,漏判err会直接 500
数据库 OFFSET/LIMIT 怎么和分页参数对齐
Fiber 本身不碰数据库,但分页逻辑常卡在 OFFSET 计算上:误用 page * limit 而不是 (page - 1) * limit,会导致第一页跳过首条数据。
假设前端传 ?page=2&limit=10,正确 offset 是 10,不是 20:
offset := (page - 1) * limit
rows, err := db.Query("SELECT * FROM users ORDER BY id DESC LIMIT ? OFFSET ?", limit, offset)- PostgreSQL 用
LIMIT $1 OFFSET $2,MySQL 同理;SQLite 语法一致 - 如果用 GORM,注意
Offset()接收的是绝对偏移量,不是页码 - 大数据量慎用 OFFSET ——
OFFSET 1000000会全表扫前百万行,此时该切游标分页
怎么返回标准分页响应结构
前端需要总条数、当前页、页大小、是否还有下一页等字段,不能只扔个数组。硬编码 key 名易出错,建议定义结构体:
type PageResponse struct {
Data interface{} `json:"data"`
Total int `json:"total"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
HasNext bool `json:"has_next"`
HasPrev bool `json:"has_prev"`
}构造时注意 TotalPages 要向上取整:
totalPages := int(math.Ceil(float64(total) / float64(limit)))
resp := PageResponse{
Data: users,
Total: total,
Page: page,
Limit: limit,
TotalPages: totalPages,
HasNext: page < totalPages,
HasPrev: page > 1,
}-
Total必须单独查一次SELECT COUNT(*),不能靠 len(data),否则无法区分“真没数据”和“刚好一页满额” - 别把
totalPages写成total / limit,整除会丢小数位,第 101 条数据时101/10=10,实际要 11 页 - HTTP 响应头里可加
X-Total-Count: 101,方便前端分页组件复用
为什么不能省略 COUNT 查询
有人想“先查 11 条,如果第 11 条存在就说明有下一页”,看似省一次 COUNT,实则埋雷:
- 并发写入时,两次查询间数据可能变动,
has_next判断失真 - 用户快速翻页时,第 11 条可能是新插入的,导致页面重复或跳变
- ORM 如 GORM 的
Limit(11).Offset(...)返回 11 条,你得手动切前 10 条再判断,代码冗余且易错
COUNT 查询在索引健全时极快(特别是覆盖索引),比业务逻辑错乱的成本低得多。真要优化,应建好 WHERE 条件字段的复合索引,而不是砍 COUNT。
分页最易忽略的其实是缓存穿透——当 page 极大(如 100000)时,几乎必然击穿缓存查库。上线前务必加 page 上限校验,别只信前端传来的值。


















