pagination

package
v0.36.0 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: 页数超出上限(疑似上游恒返整页)")
)

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

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