Files
backend/internal/pkg/cursor/cursor.go
lafay 92babe509f
All checks were successful
Build Backend / build (push) Successful in 4m42s
Build Backend / build-docker (push) Successful in 3m53s
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
2026-03-20 23:03:23 +08:00

153 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cursor
import (
"encoding/base64"
"encoding/json"
"errors"
)
// SortOrder 游标排序类型
type SortOrder int
const (
// SortByCreatedAtDesc 按创建时间降序
SortByCreatedAtDesc SortOrder = iota
// SortByCreatedAtAsc 按创建时间升序
SortByCreatedAtAsc
// SortByIDDesc 按ID降序
SortByIDDesc
// SortByIDAsc 按ID升序
SortByIDAsc
// SortBySeqDesc 按序列号降序(用于消息)
SortBySeqDesc
// SortBySeqAsc 按序列号升序
SortBySeqAsc
// SortByUpdatedAtDesc 按更新时间降序
SortByUpdatedAtDesc
// SortByUpdatedAtAsc 按更新时间升序
SortByUpdatedAtAsc
// SortByJoinTimeDesc 按加入时间降序(用于群成员)
SortByJoinTimeDesc
// SortByJoinTimeAsc 按加入时间升序(用于群成员)
SortByJoinTimeAsc
)
// String 返回排序类型的字符串表示
func (s SortOrder) String() string {
switch s {
case SortByCreatedAtDesc:
return "created_at DESC"
case SortByCreatedAtAsc:
return "created_at ASC"
case SortByIDDesc:
return "id DESC"
case SortByIDAsc:
return "id ASC"
case SortBySeqDesc:
return "seq DESC"
case SortBySeqAsc:
return "seq ASC"
case SortByUpdatedAtDesc:
return "updated_at DESC"
case SortByUpdatedAtAsc:
return "updated_at ASC"
case SortByJoinTimeDesc:
return "join_time DESC"
case SortByJoinTimeAsc:
return "join_time ASC"
default:
return "created_at DESC"
}
}
// IsDescending 是否为降序
func (s SortOrder) IsDescending() bool {
return s == SortByCreatedAtDesc || s == SortByIDDesc || s == SortBySeqDesc || s == SortByUpdatedAtDesc || s == SortByJoinTimeDesc
}
// SortField 返回排序字段名
func (s SortOrder) SortField() string {
switch s {
case SortByCreatedAtDesc, SortByCreatedAtAsc:
return "created_at"
case SortByIDDesc, SortByIDAsc:
return "id"
case SortBySeqDesc, SortBySeqAsc:
return "seq"
case SortByUpdatedAtDesc, SortByUpdatedAtAsc:
return "updated_at"
case SortByJoinTimeDesc, SortByJoinTimeAsc:
return "join_time"
default:
return "created_at"
}
}
// cursorJSON 游标 JSON 结构(用于序列化)
type cursorJSON struct {
SortValue string `json:"sv"` // 排序字段值时间戳或ID
ID string `json:"id"` // 记录唯一ID
Order SortOrder `json:"so"` // 排序方式
}
// Cursor 游标数据结构
type Cursor struct {
SortValue string // 排序字段值时间戳或ID
ID string // 记录唯一ID
Order SortOrder // 排序方式
}
// Encode 编码游标为 Base64URL 字符串
func (c *Cursor) Encode() string {
if c == nil {
return ""
}
data, err := json.Marshal(&cursorJSON{
SortValue: c.SortValue,
ID: c.ID,
Order: c.Order,
})
if err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(data)
}
// DecodeCursor 解码 Base64URL 字符串为游标
func DecodeCursor(encoded string) (*Cursor, error) {
if encoded == "" {
return nil, nil
}
data, err := base64.URLEncoding.DecodeString(encoded)
if err != nil {
// 尝试使用 RawURLEncoding不带填充的编码
data, err = base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
return nil, errors.New("invalid cursor encoding: " + err.Error())
}
}
var cj cursorJSON
if err := json.Unmarshal(data, &cj); err != nil {
return nil, errors.New("invalid cursor format: " + err.Error())
}
return &Cursor{
SortValue: cj.SortValue,
ID: cj.ID,
Order: cj.Order,
}, nil
}
// NewCursor 创建新的游标
func NewCursor(sortValue, id string, order SortOrder) *Cursor {
return &Cursor{
SortValue: sortValue,
ID: id,
Order: order,
}
}