feat(api): add cursor-based pagination for posts, comments, messages, groups, and notifications
Implement cursor-based pagination across multiple API endpoints to improve performance and enable efficient infinite scrolling. This replaces traditional offset-based pagination for high-volume data retrieval. Changes: - Add cursor pagination DTOs for all entity types (Post, Comment, Message, Conversation, Group, Notification, GroupMember, GroupAnnouncement) - Implement cursor pagination methods in repositories with support for various sort orders (created_at, updated_at, join_time, seq) - Add cursor pagination handlers that maintain backward compatibility with existing offset pagination - Add new API routes for cursor-based endpoints (/cursor suffix) - Add helper converter functions for pointer slice types in DTO conversions
This commit is contained in:
138
internal/pkg/cursor/pagination.go
Normal file
138
internal/pkg/cursor/pagination.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package cursor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultPageSize 默认页面大小
|
||||
DefaultPageSize = 20
|
||||
// MaxPageSize 最大页面大小
|
||||
MaxPageSize = 100
|
||||
)
|
||||
|
||||
// Direction 分页方向
|
||||
type Direction string
|
||||
|
||||
const (
|
||||
// Forward 向前分页(下一页)
|
||||
Forward Direction = "forward"
|
||||
// Backward 向后分页(上一页)
|
||||
Backward Direction = "backward"
|
||||
)
|
||||
|
||||
// IsValid 检查分页方向是否有效
|
||||
func (d Direction) IsValid() bool {
|
||||
return d == Forward || d == Backward
|
||||
}
|
||||
|
||||
// PageRequest 游标分页请求参数
|
||||
type PageRequest struct {
|
||||
Cursor string // 游标字符串
|
||||
Direction Direction // 分页方向
|
||||
PageSize int // 每页数量
|
||||
}
|
||||
|
||||
// NewPageRequest 创建新的游标请求
|
||||
func NewPageRequest(cursor string, direction Direction, pageSize int) *PageRequest {
|
||||
req := &PageRequest{
|
||||
Cursor: cursor,
|
||||
Direction: direction,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
req.Normalize()
|
||||
return req
|
||||
}
|
||||
|
||||
// Normalize 规范化分页参数
|
||||
func (r *PageRequest) Normalize() {
|
||||
if r.Direction == "" {
|
||||
r.Direction = Forward
|
||||
}
|
||||
if r.PageSize <= 0 {
|
||||
r.PageSize = DefaultPageSize
|
||||
}
|
||||
if r.PageSize > MaxPageSize {
|
||||
r.PageSize = MaxPageSize
|
||||
}
|
||||
}
|
||||
|
||||
// GetPageSize 获取有效的页面大小(默认20,最大100)
|
||||
func (r *PageRequest) GetPageSize() int {
|
||||
if r.PageSize <= 0 {
|
||||
return DefaultPageSize
|
||||
}
|
||||
if r.PageSize > MaxPageSize {
|
||||
return MaxPageSize
|
||||
}
|
||||
return r.PageSize
|
||||
}
|
||||
|
||||
// Validate 验证分页请求
|
||||
func (r *PageRequest) Validate() error {
|
||||
if r.Direction != "" && !r.Direction.IsValid() {
|
||||
return errors.New("invalid direction, must be 'forward' or 'backward'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCursor 解码游标
|
||||
func (r *PageRequest) GetCursor() (*Cursor, error) {
|
||||
return DecodeCursor(r.Cursor)
|
||||
}
|
||||
|
||||
// IsForward 是否向前分页
|
||||
func (r *PageRequest) IsForward() bool {
|
||||
return r.Direction == Forward || r.Direction == ""
|
||||
}
|
||||
|
||||
// IsBackward 是否向后分页
|
||||
func (r *PageRequest) IsBackward() bool {
|
||||
return r.Direction == Backward
|
||||
}
|
||||
|
||||
// PageResponse 游标分页响应结构
|
||||
type PageResponse struct {
|
||||
Items interface{} `json:"items"`
|
||||
NextCursor string `json:"next_cursor"`
|
||||
PrevCursor string `json:"prev_cursor,omitempty"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
// NewPageResponse 创建分页响应
|
||||
func NewPageResponse(items interface{}, nextCursor, prevCursor string, hasMore bool) *PageResponse {
|
||||
return &PageResponse{
|
||||
Items: items,
|
||||
NextCursor: nextCursor,
|
||||
PrevCursor: prevCursor,
|
||||
HasMore: hasMore,
|
||||
}
|
||||
}
|
||||
|
||||
// CursorPageResult 游标分页结果(泛型版本,供内部使用)
|
||||
type CursorPageResult[T any] struct {
|
||||
Items []T
|
||||
NextCursor string
|
||||
PrevCursor string
|
||||
HasMore bool
|
||||
}
|
||||
|
||||
// NewCursorPageResult 创建游标分页结果
|
||||
func NewCursorPageResult[T any](items []T, nextCursor, prevCursor string, hasMore bool) *CursorPageResult[T] {
|
||||
return &CursorPageResult[T]{
|
||||
Items: items,
|
||||
NextCursor: nextCursor,
|
||||
PrevCursor: prevCursor,
|
||||
HasMore: hasMore,
|
||||
}
|
||||
}
|
||||
|
||||
// ToPageResponse 转换为通用分页响应
|
||||
func (r *CursorPageResult[T]) ToPageResponse() *PageResponse {
|
||||
return &PageResponse{
|
||||
Items: r.Items,
|
||||
NextCursor: r.NextCursor,
|
||||
PrevCursor: r.PrevCursor,
|
||||
HasMore: r.HasMore,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user