分页参数需用strconv.Atoi安全转换并校验边界:page默认1且不低于1,size限制10–100;SQL用ORDER BY created_at DESC + LIMIT ? OFFSET ?;返回结构体含total、hasNext等元信息;避免大OFFSET,改用游标分页。
用户访问 /messages?page=2&size=10 时,page 和 siz 必须转成整数并做边界校验,否则直接传给数据库会出错或被攻击。
e
page 默认为 1,小于 1 的值强制设为 1size 建议限制在 10–100 之间,超出则用上限值(比如 50),避免拖垮数据库strconv.Atoi 转换后必须检查错误,不能忽略返回的 err
url.QueryEscape 处理分页参数——那是用于生成 URL 的,不是用于解析Go 中用 database/sql 执行分页查询,核心是构造带 OFFSET 和 LIMIT 的语句。注意:MySQL 和 PostgreSQL 语法一致,但 SQLite 的 OFFSET 必须配合 LIMIT 使用,且不能省略 LIMIT。
OFFSET 值 = (page - 1) * size,不是 page * size
Limit().Offset() 链式调用ORDER BY created_at DESC),否则翻页会重复或漏数据SELECT id, content, author, created_at FROM messages ORDER BY created_at DESC LIMIT ? OFFSET ?
只返回 []Message 不够,前端需要知道总条数、当前页、每页几条、有没有下一页。建议封装一个结构体,而不是用 map[string]interface{}。
SELECT COUNT(*) FROM messages,不能靠 len(结果切片)totalPages := (total + size - 1) / size,用整数运算避免浮点误差hasNext 和 hasPrev 字段,这两个比算页码更可靠type PageResult struct {
Data []Message `json:"data"`
Total int `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
TotalPages int `json:"total_pages"`
HasNext bool `json:"has_next"`
HasPrev bool `json:"has_prev"`
}当 OFFSET 很大时(比如第 1000 页,每页 10 条),MySQL 仍要扫描前 10000 行再丢弃,I/O 和 CPU 开销陡增。
立即学习“go语言免费学习笔记(深入)”;
created_at 和 id 作为条件,例如 WHERE created_at
OFFSET,确保 ORDER BY 字段有索引(如 INDEX(created_at, id))cursor 或物化视图缓解,但 Go 层逻辑不变实际部署时,最容易被忽略的是游标分页的边界处理:当两条记录 created_at 完全相同时,仅靠时间无法区分顺序,必须加入主键(如 id)做第二排序和过滤条件。