分页与列表接口设计
列表接口是最高频的接口形态,核心诉求就两条:能分页、能拿到总数。GoFrame ORM 的分页链式方法是 Limit(offset, limit) 与 Page(page, size);而“查列表 + 数总数”这种组合查询,官方从 v2.5 起提供了 AllAndCount / ScanAndCount 两个便捷方法,本章结合规范路由演示完整设计。
请求与响应的分页字段
规范路由下,分页参数直接放进 Req,用校验规则约束取值范围:
package api
import "github.com/gogf/gf/v2/frame/g"
// ArticleGetListReq 列表请求:page/size 带默认值
type ArticleGetListReq struct {
g.Meta `path:"/articles" method:"get" tags:"Article" summary:"article list"`
Page int `d:"1" v:"min:1" dc:"页码"`
Size int `d:"10" v:"between:1,100" dc:"每页数量"`
Status int `dc:"状态过滤,0 不过滤"`
}
响应建议固定为“列表 + 总数 + 当前分页”,方便前端计算总页数:
type ArticleGetListRes struct {
List []*ArticleItem `json:"list"`
Total int `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
type ArticleItem struct {
Id int `json:"id"`
Title string `json:"title"`
}
ORM 分页查询
package logic
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"project/api"
"project/internal/dao"
)
func GetArticleList(ctx context.Context, in *api.ArticleGetListReq) (out *api.ArticleGetListRes, err error) {
m := dao.Article.Ctx(ctx)
if in.Status > 0 {
m = m.Where("status", in.Status)
}
out = &api.ArticleGetListRes{Page: in.Page, Size: in.Size}
// ScanAndCount:一条链完成 分页查询 + COUNT(1)
// 第 3 个参数 false 表示总数用 COUNT(1),不携带查询字段
err = m.Page(in.Page, in.Size).OrderDesc("id").ScanAndCount(&out.List, &out.Total, false)
if err != nil {
return nil, err
}
return out, nil
}
ScanAndCount(&list, &total, useFieldForCount) 的内部逻辑相当于 Scan 查列表 + 去掉 Limit/Page 后 Count 总数,一步到位。也可用 AllAndCount 返回 Result(不映射结构体)的场景。传统写法 Limit(offset, size) 配合单独 Count() 仍然可用,但要多写两行与“去掉分页条件”的重复代码。
控制器接线
package v1
import (
"context"
"project/api"
"project/internal/logic"
)
type Controller struct{}
func (c *Controller) GetList(ctx context.Context, req *api.ArticleGetListReq) (res *api.ArticleGetListRes, err error) {
return logic.GetArticleList(ctx, req)
}
注册 group.Bind(new(v1.Controller)) 后,GET /articles?page=2&size=20 即返回统一包装的分页结构。
注意点
- 不要让客户端传超大
size:校验上限 + 服务端再次封顶双保险。 - 排序字段默认用主键倒序保证稳定;自定义排序务必白名单,防止把任意列名拼进
Order。 Count不计入Limit/Page,这是ScanAndCount的既定行为;带GROUP BY/子查询时总数可能不等于行数,需单独设计计数。- 前端“总页数”由
Total/Size现算即可,不必冗余存储。
小结
分页接口的推荐套路:Req 里用 d 标签给默认值并加 v 校验,Res 固定 List/Total/Page/Size,查询端用 Page + ScanAndCount 一条链收尾。方法语义与版本要求(v2.5+)以官方文档 goframe.org 为准。