Gin中应使用c.Query()解析URL分页参数,page和page_size需转为整数并设默认值:page默认1、page_size默认10,避免用strconv.Atoi硬转导致逻辑错误。

分页参数怎么从 URL 解析出来
Gin 里拿 page 和 page_size 最常用的是 c.Query(),不是 c.Param()(那是路径参数),也不是 c.PostForm()(那是表单提交)。URL 像 /api/users?page=2&page_size=10 才能被正确读取。
注意默认值和类型转换:
-
page和page_size必须转成整数,c.Query()返回的是字符串,直接用会 panic - 别用
strconv.Atoi(c.Query("page"))硬转——没传时返回 0,但第 0 页没意义,应该设默认值 1 - 建议用
strconv.ParseInt()+ 显式 fallback,比如page, _ := strconv.ParseInt(c.Query("page"), 10, 64); if page -
page_size要限制上限(比如最大 100),否则恶意请求page_size=1000000可能拖垮数据库
SQL 查询怎么写才不越界、不漏数据
MySQL 用 LIMIT + OFFSET 最直接,但要注意:偏移量大时性能差(比如 OFFSET 100000),而且如果数据有增删,页与页之间可能跳过或重复。
更稳的做法是「游标分页」(cursor-based),但对简单管理后台,传统分页够用。关键点:
- 计算
OFFSET:应为(page - 1) * page_size,不是page * page_size - 查总数不能只靠
COUNT(*)全表扫——加 WHERE 条件后必须保持一致,否则总数和列表数据对不上 - 推荐先查总数(带相同 WHERE),再查分页数据;或者用子查询/CTE,但注意 ORM 是否支持
- GORM 示例:
db.Model(&User{}).Where("status = ?", "active").Count(&total),然后db.Offset(int(offset)).Limit(int(pageSize)).Find(&users)
响应结构怎么组织才符合前端预期
前端通常需要三个字段:data(当前页数据)、pagination(分页元信息)、code/message(状态)。别把总数塞进 data 里,也别让前端自己算 total_pages。
推荐 pagination 字段包含:
-
page:当前页码(返回原始输入值,哪怕它超出范围) -
page_size:实际使用的页大小(可能被服务端截断,如请求 200 → 返回 100) -
total:总记录数(必须是精确值,不是估算) -
total_pages:可选,但建议算好返回:(total + page_size - 1) / page_size(向上取整)
错误情况也要明确:比如 page=0 或 page_size=0,应返回 400 和提示,而不是静默修正。
为什么 COUNT(*) 和 LIMIT 查询要复用同一个 WHERE 条件
这是最容易出错的地方。常见写法是:先写一个带复杂条件的列表查询(比如多表 JOIN + 多个 WHERE),然后另写一个 COUNT 查询,只改了 SELECT 部分——结果 COUNT 漏了某个 JOIN 或 WHERE,导致总数比实际数据多。
例如:
SELECT u.* FROM users u LEFT JOIN profiles p ON u.id = p.user_id WHERE p.active = true
对应 COUNT 必须是:
SELECT COUNT(*) FROM users u LEFT JOIN profiles p ON u.id = p.user_id WHERE p.active = true
而不是:
SELECT COUNT(*) FROM users WHERE active = true
后者完全错了——既没 JOIN,WHERE 条件也错配。
用 GORM 的话,建议把查询条件抽成函数或链式调用变量,COUNT 和 Find 共用同一对象,避免手写两遍 SQL。
分页看着简单,但 page 和 page_size 的校验边界、COUNT 与主查询条件的一致性、以及总数是否真实反映过滤逻辑——这三点漏掉任何一个,前端翻页就会出现空白页、重复项或“最后一页还有数据”的诡异现象。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











