paginate 不是 gorm 内置方法,企业级云盘分页必须手动控制 offset 和 limit,校验 page/pagesize 边界、用 int64 防溢出、独立 count 复用条件、路径字段建前缀索引,并拆解树状查询避免 preload 膨胀。

Paginate 不是 GORM 内置方法,别被封装库或旧教程带偏——企业级云盘的文件列表分页必须自己控 Offset 和 Limit,否则在深度分页、高并发目录树展开时会查慢、漏数据、甚至 panic。
为什么不能直接用 gorm/pagination 插件
这个插件确实提供了 Paginate 方法,但它只是把 Offset 和 Limit 封了一层,没解决本质问题:
- page=0 或 pageSize=0 时,
Offset(-1)会直接 panic,而插件默认不校验 - 它对
Preload关联查询不做隔离,云盘里一个目录下有 500 个文件,每个文件又关联 3 个版本记录,Preload("Versions")会一次性拉出 1500+ 条记录,远超分页预期 - GORM v2.2+ 修改了 Scope 执行顺序,老版本插件(如
github.com/robertkrimen/gorm-paginator)在事务中可能漏掉 WHERE 条件 - 它无法适配云盘特有的“按路径前缀 + 排序 + 类型过滤”复合查询,比如
WHERE path LIKE '/team/a/%' AND is_dir = false ORDER BY updated_at DESC
Limit + Offset 分页必须手动校验边界
云盘接口常暴露给前端或移动端,参数不可信,硬编码容错逻辑比依赖插件更可控:
- page 必须 ≥ 1,否则设为 1:
if page - pageSize 必须 > 0 且 ≤ 100(防恶意刷大页):
if pageSize 100 { pageSize = 20 } - offset 计算必须用 int64 防溢出:
offset := int64((page - 1) * pageSize)(尤其当 page 达到万级时,int 可能溢出) - MySQL 中
Limit 0会返回空结果,但 SQLite 会返回全表——统一用校验兜底,不依赖数据库行为
树状目录展示要拆开查,不能靠单次 Preload
云盘的“目录树”不是扁平列表,而是需要递归展开子目录(含层级、是否可展开、子项数),强行用 Preload("Children") 会导致 N+1 或爆炸式数据膨胀:
- 正确做法:先查当前目录下的直接子项(分页),再单独发一条 COUNT 查询统计各子目录的 children_count
- 例如:查
/project/x/下第 1 页 20 个子项,同时执行SELECT parent_path, COUNT(*) FROM files WHERE parent_path IN (?, ?, ...) GROUP BY parent_path - 避免在分页主查询里
JOIN子目录表,否则 MySQL 优化器容易选错索引,尤其是path字段没建前缀索引时 - 对“是否为目录”字段(如
is_dir)加索引,并在 WHERE 条件中显式写出is_dir = true,让索引生效
总数统计必须独立执行 Count,且加相同 WHERE
云盘接口返回的 total 是用户真正关心的“该路径下全部文件/目录数”,它和分页查询的 WHERE 条件必须完全一致:
- 错误写法:
db.Where("path LIKE ?", prefix+"%").Count(&total),但分页查的是WHERE path LIKE ? AND is_dir = false→ total 错了 - 正确写法:把查询条件抽成函数,复用到 Count 和 Find:
cond := db.Where("parent_path = ?", path).Where("deleted_at IS NULL")cond.Count(&total)cond.Offset(offset).Limit(pageSize).Find(&files)- 注意:GORM 的
Count不走缓存,也不支持Preload,所以它天然轻量;别试图用Find查完再 len(),那会拉全量数据
最易被忽略的一点:云盘路径字段(如 path VARCHAR(1024))必须建前缀索引,比如 INDEX idx_path ON files (path(255)),否则 LIKE '/user/123/%' 查询永远走不了索引,分页性能随数据增长断崖下跌。











