Documentation
¶
Overview ¶
Package pagination 提供框架无关的分页参数定义。
本包是 PageRequest 的唯一定义位置,middleware 和 data/gormx 均依赖本包, 避免 middleware 层反向依赖 data 层。
Index ¶
Constants ¶
const ( // DefaultPage 是默认页码。 DefaultPage = 1 // DefaultPageSize 是默认每页条数。 DefaultPageSize = 20 // MaxPageSize 是每页最大条数。 MaxPageSize = 500 )
const MaxWalkPages = 10000
MaxWalkPages 单次 Walk 的页数上限:按最小可用页长 1 计也远超真实 API 的 数据量,唯一目的是把「上游恒返整页」这类契约违例从死循环变成明确报错。
Variables ¶
var ( // ErrBadPageSize pageSize 非法(<=0)。 ErrBadPageSize = pkgerrors.NewBizError(1, "pagination: pageSize 必须为正数") // ErrNilFetcher fetch 回调为 nil。 ErrNilFetcher = pkgerrors.NewBizError(1, "pagination: fetch 不能为 nil") // ErrTooManyPages 页数超出 MaxWalkPages——上游违反短页契约(越界页恒返 // 整页)或数据量真的越界,两者都需要人来看。 ErrTooManyPages = pkgerrors.NewBizError(1, "pagination: 页数超出上限(疑似上游恒返整页)") )
var ErrBudgetExceeded = pkgerrors.NewBizError(1, "pagination: 页数预算耗尽(行走未自然结束)")
ErrBudgetExceeded 是 WalkEach 的页数预算哨兵(#211):行走因 budget 上限 停止而非自然取完(短页)。调用方 errors.Is 判定后自行决定加大预算重扫 或接受部分结果——被预算截断的行走**不完整**,静默当全量用是消费方的 事故(上游生态的无预算全量扫描形态后来自己都要在 doc 里提醒「列表多 时每次都是全量扫描」,预算是防失控护栏,不是可选优化)。
var ErrNilYielder = pkgerrors.NewBizError(1, "pagination: yield 不能为 nil")
ErrNilYielder 是 WalkEach 的 yield 回调为 nil 哨兵(同 ErrNilFetcher 口径)。
Functions ¶
func Walk ¶ added in v0.36.0
func Walk[T any](ctx context.Context, pageSize int, fetch func(ctx context.Context, page int) ([]T, error)) ([]T, error)
Walk 翻页聚合单源:以固定 pageSize 从第 1 页起逐页调 fetch,把全部元素 按页序拼接返回。终止条件是**短页即末页**——某页返回的元素数少于 pageSize (含 0)即为最后一页,此后不再请求。
契约(fetch 一侧):
- page 从 1 计数,fetch 自行把页码与页长翻译成具体 API 的查询参数;
- fetch 必须**诚实短页**:末页返回实际条数而非凑满 pageSize。上游 API 若在越界页恒返整页,终止条件永不满足,将撞 MaxWalkPages 上限返回 ErrTooManyPages——这是防呆,不是功能;
- 任何一页 fetch 失败立即返回该页错误(带页码上下文),已聚合的部分 不返回——调用方要么拿到全量要么拿到错误,没有半截结果;
- 逐页做 ctx 取消检查,长翻页调用方传值即可获得取消语义。
不做并发预取:上游 API 多数按页序有 token/游标依赖,收益不值得复杂度。 此前 zentao-mini 的 client_* 五个「翻页直到 len<pageSize」循环各写一份 (页长 100/200/500 各异),下沉为一份原语(yi-nology/zentao-mini#22)。
func WalkEach ¶ added in v0.37.0
WalkEach 按页行走第三方分页 API 的**流式形态**(#211)——与 Walk (翻页聚合单源)同族两个入口:
- Walk:收集全量返回([]T, error),短页按 pageSize 判定,适合「取 全量再处理」;
- WalkEach:逐条经 yield 回调流出,返回 false **提前终止整个行走** (不是跳页)——「找到即停」的搜索型消费(按 tag 找 release)不必 扫全量;适合大结果集与流式处理。
语义(三家下游独立实现收敛出的取舍):
- 页号从 1 起、逐页递增;fetch(page) 返回该页条目(回调式而非接口式 ——消费方的 fetch 都是闭包,捕获各自的 SDK client 与 perPage);
- **短页停止**:len(items) < 上一页长度 即最后一页——不多打一次空页 请求;首页为空直接结束;
- budget 是页数上限(防失控护栏):fetch 页数达到 budget 仍未走到 短页时返回 ErrBudgetExceeded。budget <= 0 表示无预算(不建议—— 上游恒返整页时行走不终止;对照 Walk 的 MaxWalkPages 硬防呆);
- yield 返回 false 提前终止,返回 nil(主动终止不是错误);
- fetch 返回错误中止行走,错误带页码上下文包装(errors.Is 链路保持);
- 不做并发预取(同 Walk——上游 API 多数按页序有 token/游标依赖)。
fetch/yield 为 nil 返回 ErrNilFetcher / ErrNilYielder。游标制 API 的 行走是另一个域,不在本原语范围。需要 ctx 取消语义的调用方在 fetch / yield 闭包内自行检查(本形态的 fetch 不携带 ctx——它与 Walk 的差别 恰在「行走过程不占用 ctx 生命周期」)。
用法:
var found *Release
err := pagination.WalkEach(50,
func(page int) ([]*Release, error) { return client.ListReleases(ctx, page, 100) },
func(r *Release) bool {
if hasTag(r, want) {
found = r
return false // 找到即停
}
return true
},
)
Types ¶
type OverMaxPolicy ¶ added in v0.25.2
type OverMaxPolicy int
OverMaxPolicy 描述「请求页长超过上限」时的处理语义。
两种语义都真实存在于消费方,且**响应条数不同**(可观测差异,不能顺手统一):
- OverMaxClamp:钳到 MaxPageSize(pkg/pagination 现行语义)。
- OverMaxFallback:回退到 DefaultPageSize。
const ( // OverMaxClamp 把超限的页长钳到 MaxPageSize。 // 这是 OverMaxPolicy 的零值,也是 pkg/pagination 的现行语义。 OverMaxClamp OverMaxPolicy = iota // OverMaxFallback 把超限的页长回退到 DefaultPageSize。 OverMaxFallback )
type PageRequest ¶
type PageRequest struct {
Page int `json:"page" query:"page" form:"page"`
PageSize int `json:"page_size" query:"page_size" form:"page_size"`
}
PageRequest 表示分页查询请求参数。
func NewPageRequest ¶
func NewPageRequest(page, pageSize int) *PageRequest
NewPageRequest 根据页码和每页条数创建 PageRequest。
func (*PageRequest) Normalize ¶
func (r *PageRequest) Normalize()
Normalize 规范化分页参数,为非法值设置默认值,并限制最大每页条数。
func (*PageRequest) Offset ¶
func (r *PageRequest) Offset() int
Offset 返回分页查询的偏移量。 当计算结果溢出时返回 0(回退到第一页)。 只读:不在接收者上落 Normalize 的写 — 共享 PageRequest(如全局默认值) 并发调 Offset/Limit 时是数据竞争(2026-09-06 复审修复)。
type PageResponse ¶
type PageResponse[T any] struct { Items []T `json:"items"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` Pages int `json:"pages"` }
PageResponse 是全库统一的分页响应信封。
此前 data/gormx(JSON "list")与 data/crud(JSON "items")各有一个 字段名/JSON 契约不同的泛型分页结构,导致 API 契约分叉;两者已统一 为本类型的类型别名,新代码直接使用 pagination.PageResponse。
字段取舍说明:数据字段名采用 items(与 handler/crudh 的 REST 契约 一致),pages 保留 gormx 语义(总页数,向上取整)。
type Policy ¶ added in v0.25.2
type Policy struct {
// DefaultPage 是非法页码(<= 0)时归一化到的页码。
DefaultPage int
// DefaultPageSize 是非法页长(<= 0)时归一化到的页长。
DefaultPageSize int
// MaxPageSize 是页长上限;< 0 表示不限上限。
MaxPageSize int
// OverMax 是页长超上限时的处理语义。
OverMax OverMaxPolicy
}
Policy 是服务级分页口径——把「默认页长 / 上限 / 超限语义」从包级常量里提出来, 让每个服务声明自己的口径,而不是各写一份归一化逻辑。
零值退回包级默认(DefaultPage / DefaultPageSize / MaxPageSize / OverMaxClamp), 因此 `Policy{}` 与包级常量口径完全一致。
字段语义:
- DefaultPage:<= 0 时取包级 DefaultPage。
- DefaultPageSize:<= 0 时取包级 DefaultPageSize。
- MaxPageSize:== 0 时取包级 MaxPageSize;< 0 表示**不限上限**(如「默认 20、不设上限」 的服务);> 0 时为该服务的页长上限。
- OverMax:页长超上限时的语义;未知取值按 OverMaxClamp 处理。
例:默认 50、上限 200、超限回退默认(等价于把口径写死在服务里):
Policy{DefaultPageSize: 50, MaxPageSize: 200, OverMax: OverMaxFallback}
例:默认 20、不限上限:
Policy{DefaultPageSize: 20, MaxPageSize: -1}
func (Policy) Normalize ¶ added in v0.25.2
Normalize 按本口径归一化页码与页长,返回归一化后的 (page, pageSize)。
规则:
- page <= 0 ⇒ 口径的默认页码。
- pageSize <= 0 ⇒ 口径的默认页长。
- pageSize 超上限 ⇒ 按 OverMax 语义处理:钳到上限(OverMaxClamp) 或回退到默认页长(OverMaxFallback);口径不限上限时原样返回。
纯函数,不修改入参。
func (Policy) NormalizeLimit ¶ added in v0.28.0
NormalizeLimit 按「最多返回多少条」的口径归一化条数。
与 Normalize 的区别只在**口径维度**:Normalize 说的是「第几页、每页几条」 (两个参数);本方法说的是「最多返回多少条」——**没有页的概念**,如工具的 limit 参数(语义是行数),或任何「只要前 N 条」的场景。
规则(页长档语义完全一致,字段复用 DefaultPageSize / MaxPageSize / OverMax, 不另起一套几乎一样的口径):
- n <= 0 ⇒ 口径的默认条数。
- n 超上限 ⇒ 按 OverMax 语义处理:钳到上限(OverMaxClamp)或回退到默认条数 (OverMaxFallback);口径不限上限时原样返回。
等价于 Limit(1, n),但不必传一个恒为 1 的页码——把「第 1 页的页长」当「条数」 用是拧的:读代码的人会先愣一下才反应过来这里没有页。