pagination

package
v0.37.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package pagination 提供框架无关的分页参数定义。

本包是 PageRequest 的唯一定义位置,middleware 和 data/gormx 均依赖本包, 避免 middleware 层反向依赖 data 层。

Index

Constants

View Source
const (
	// DefaultPage 是默认页码。
	DefaultPage = 1
	// DefaultPageSize 是默认每页条数。
	DefaultPageSize = 20
	// MaxPageSize 是每页最大条数。
	MaxPageSize = 500
)
View Source
const MaxWalkPages = 10000

MaxWalkPages 单次 Walk 的页数上限:按最小可用页长 1 计也远超真实 API 的 数据量,唯一目的是把「上游恒返整页」这类契约违例从死循环变成明确报错。

Variables

View Source
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: 页数超出上限(疑似上游恒返整页)")
)
View Source
var ErrBudgetExceeded = pkgerrors.NewBizError(1, "pagination: 页数预算耗尽(行走未自然结束)")

ErrBudgetExceeded 是 WalkEach 的页数预算哨兵(#211):行走因 budget 上限 停止而非自然取完(短页)。调用方 errors.Is 判定后自行决定加大预算重扫 或接受部分结果——被预算截断的行走**不完整**,静默当全量用是消费方的 事故(上游生态的无预算全量扫描形态后来自己都要在 doc 里提醒「列表多 时每次都是全量扫描」,预算是防失控护栏,不是可选优化)。

View Source
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 各异),下沉为一份原语(上游 zentao-mini#22)。

func WalkEach added in v0.37.0

func WalkEach[T any](budget int, fetch func(page int) ([]T, error), yield func(T) bool) error

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) Limit

func (r *PageRequest) Limit() int

Limit 返回分页查询的限制条数。

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) Limit added in v0.25.2

func (p Policy) Limit(page, pageSize int) int

Limit 返回按本口径归一化后的限制条数。

func (Policy) Normalize added in v0.25.2

func (p Policy) Normalize(page, pageSize int) (int, int)

Normalize 按本口径归一化页码与页长,返回归一化后的 (page, pageSize)。

规则:

  • page <= 0 ⇒ 口径的默认页码。
  • pageSize <= 0 ⇒ 口径的默认页长。
  • pageSize 超上限 ⇒ 按 OverMax 语义处理:钳到上限(OverMaxClamp) 或回退到默认页长(OverMaxFallback);口径不限上限时原样返回。

纯函数,不修改入参。

func (Policy) NormalizeLimit added in v0.28.0

func (p Policy) NormalizeLimit(n int) int

NormalizeLimit 按「最多返回多少条」的口径归一化条数。

与 Normalize 的区别只在**口径维度**:Normalize 说的是「第几页、每页几条」 (两个参数);本方法说的是「最多返回多少条」——**没有页的概念**,如工具的 limit 参数(语义是行数),或任何「只要前 N 条」的场景。

规则(页长档语义完全一致,字段复用 DefaultPageSize / MaxPageSize / OverMax, 不另起一套几乎一样的口径):

  • n <= 0 ⇒ 口径的默认条数。
  • n 超上限 ⇒ 按 OverMax 语义处理:钳到上限(OverMaxClamp)或回退到默认条数 (OverMaxFallback);口径不限上限时原样返回。

等价于 Limit(1, n),但不必传一个恒为 1 的页码——把「第 1 页的页长」当「条数」 用是拧的:读代码的人会先愣一下才反应过来这里没有页。

func (Policy) Offset added in v0.25.2

func (p Policy) Offset(page, pageSize int) int

Offset 返回按本口径归一化后的偏移量。 归一化结果溢出(int 溢出成负数)时返回 0(回退到第一页)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL