Revel分页需手动解析参数并校验,默认值、类型转换、范围限制缺一不可;GORM需手动计算OFFSET和LIMIT,注意性能与总数查询一致性;筛选用链式Where防注入;响应须按前端要求封装PageResponse结构。

Revel 中分页参数解析必须手动处理,没有自动绑定
Revel 没有像 Gin 的 c.ShouldBindQuery 或 Django REST Framework 的 GenericAPIView 那样的内置 Query 参数结构体绑定机制。所有 page、page_size、筛选字段都得显式从 c.Params.Get("page") 或 c.Params.Get("keyword") 取值,再做类型转换和校验。
常见错误是直接用 strconv.Atoi(c.Params.Get("page")),但 c.Params.Get 在参数未传时返回空字符串,strconv.Atoi("") 会 panic。正确做法是:
- 先判断
c.Params.Get("page") == "",为空则设默认值(如 1) - 对非空字符串调用
strconv.ParseInt(..., 10, 32),并检查error - 加范围限制:页码 ≥ 1,
page_size控制在 1–100 之间,防恶意请求 - 把解析结果统一存进一个结构体(如
ListOptions),避免 handler 里散落多处转换逻辑
GORM 查询需手动拼 LIMIT + OFFSET,别依赖 PageHelper 类插件
Revel 本身不提供分页中间件,GORM 也没有内置的 paginate() 方法(不像 Laravel 或 Hyperf)。你得自己算 offset = (page - 1) * page_size,再传给 Limit().Offset():
offset := (opts.Page - 1) * opts.PageSize var users []User err := DBMain.Offset(offset).Limit(opts.PageSize).Find(&users).Error
注意几个坑:
- 别漏掉
- 1,否则第 1 页会跳过首条数据 - OFFSET 超过几十万行后性能急剧下降,大表务必考虑游标分页(比如用
id > ?替代OFFSET) - 别忘了查总数:
DBMain.Model(&User{}).Count(&total),这是前端渲染“共 X 条”和禁用下一页按钮的依据 - 若用多个 DB 实例(如分片),确保计数和查询走的是同一个连接池(
DBMain或DBShard1)
条件筛选要用 GORM 的链式构建,避免 SQL 注入
Revel + GORM 做多条件筛选,推荐用 Where() 链式调用,而不是拼 SQL 字符串。例如按状态和关键词搜索:
query := DBMain
if opts.Status != "" {
query = query.Where("status = ?", opts.Status)
}
if opts.Keyword != "" {
query = query.Where("name LIKE ?", "%"+opts.Keyword+"%")
}
query.Offset(offset).Limit(opts.PageSize).Find(&users)
关键点:
- 每个
Where()只传一个参数值,GORM 自动预处理,杜绝 SQL 注入 - LIKE 查询中,通配符
%必须写在 Go 字符串里,不能放进 SQL 模板(如"name LIKE '%?%'"是错的) - 若需 IN 查询(如
status IN (?, ?, ?)),用sqlx.In或 GORM 的Where("status IN ?", statuses)(statuses是切片) - 字段名硬编码易出错,建议用常量或结构体字段映射,比如
const UserStatusCol = "status"
分页响应结构要兼容前端表格组件(如 react-bootstrap-table2)
前端表格库(如 react-bootstrap-table2)通常期望接口返回固定结构的 JSON,比如含 data、totalSize、sizePerPage 等字段。Revel 的 controller 里别直接返回 users 切片,应封装成标准分页对象:
type PageResponse struct {
Data interface{} `json:"data"`
TotalSize int64 `json:"totalSize"`
SizePerPage int `json:"sizePerPage"`
Page int `json:"page"`
}
c.RenderJSON(PageResponse{
Data: users,
TotalSize: total,
SizePerPage: opts.PageSize,
Page: opts.Page,
})
这个结构直接影响前端能否自动识别分页元信息。容易被忽略的是:
-
TotalSize必须是int64,因为 COUNT(*) 可能超int范围 - 字段名大小写要和前端约定一致(如
totalSize≠total_size) - 若启用了数据库分片,总数查询和数据查询必须在同一分片上执行,否则总数不准

















