245 lines
5.2 KiB
Go
245 lines
5.2 KiB
Go
|
|
package cursor
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"gorm.io/gorm"
|
||
|
|
)
|
||
|
|
|
||
|
|
// CursorBuilder 游标查询构建器
|
||
|
|
type CursorBuilder struct {
|
||
|
|
db *gorm.DB
|
||
|
|
cursor *Cursor
|
||
|
|
direction Direction
|
||
|
|
pageSize int
|
||
|
|
order SortOrder
|
||
|
|
err error
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewBuilder 创建新的构建器
|
||
|
|
func NewBuilder(db *gorm.DB, order SortOrder) *CursorBuilder {
|
||
|
|
return &CursorBuilder{
|
||
|
|
db: db,
|
||
|
|
order: order,
|
||
|
|
pageSize: DefaultPageSize,
|
||
|
|
direction: Forward,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// WithCursor 设置游标
|
||
|
|
func (b *CursorBuilder) WithCursor(cursor string, direction Direction) *CursorBuilder {
|
||
|
|
if b.err != nil {
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
if cursor == "" {
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
decoded, err := DecodeCursor(cursor)
|
||
|
|
if err != nil {
|
||
|
|
b.err = err
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
b.cursor = decoded
|
||
|
|
b.direction = direction
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
// WithPageSize 设置页面大小
|
||
|
|
func (b *CursorBuilder) WithPageSize(size int) *CursorBuilder {
|
||
|
|
if b.err != nil {
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
if size <= 0 {
|
||
|
|
b.pageSize = DefaultPageSize
|
||
|
|
} else if size > MaxPageSize {
|
||
|
|
b.pageSize = MaxPageSize
|
||
|
|
} else {
|
||
|
|
b.pageSize = size
|
||
|
|
}
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
// WithDirection 设置分页方向
|
||
|
|
func (b *CursorBuilder) WithDirection(direction Direction) *CursorBuilder {
|
||
|
|
if b.err != nil {
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
if direction == "" {
|
||
|
|
b.direction = Forward
|
||
|
|
} else {
|
||
|
|
b.direction = direction
|
||
|
|
}
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
// Error 返回构建过程中的错误
|
||
|
|
func (b *CursorBuilder) Error() error {
|
||
|
|
return b.err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Build 构建查询(返回 GORM 查询)
|
||
|
|
// 注意:调用者需要自己执行 Find 并处理结果
|
||
|
|
func (b *CursorBuilder) Build() *gorm.DB {
|
||
|
|
if b.err != nil {
|
||
|
|
return b.db.Where("1 = 0") // 返回空结果
|
||
|
|
}
|
||
|
|
|
||
|
|
query := b.db
|
||
|
|
|
||
|
|
// 应用游标条件
|
||
|
|
if b.cursor != nil {
|
||
|
|
query = b.applyCursorCondition(query)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 应用排序
|
||
|
|
query = b.applyOrder(query)
|
||
|
|
|
||
|
|
// 多取一条用于判断是否有更多数据
|
||
|
|
query = query.Limit(b.pageSize + 1)
|
||
|
|
|
||
|
|
return query
|
||
|
|
}
|
||
|
|
|
||
|
|
// applyCursorCondition 应用游标条件
|
||
|
|
func (b *CursorBuilder) applyCursorCondition(query *gorm.DB) *gorm.DB {
|
||
|
|
if b.cursor == nil {
|
||
|
|
return query
|
||
|
|
}
|
||
|
|
|
||
|
|
sortField := b.order.SortField()
|
||
|
|
sortValue := b.cursor.SortValue
|
||
|
|
id := b.cursor.ID
|
||
|
|
|
||
|
|
// 根据分页方向和排序类型决定比较运算符
|
||
|
|
var op string
|
||
|
|
if b.direction == Forward {
|
||
|
|
// 向前分页:降序用 <,升序用 >
|
||
|
|
if b.order.IsDescending() {
|
||
|
|
op = "<"
|
||
|
|
} else {
|
||
|
|
op = ">"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// 向后分页:降序用 >,升序用 <
|
||
|
|
if b.order.IsDescending() {
|
||
|
|
op = ">"
|
||
|
|
} else {
|
||
|
|
op = "<"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 使用元组比较:(sort_field, id) op (sort_value, id)
|
||
|
|
// 这样可以确保排序稳定且唯一
|
||
|
|
condition := fmt.Sprintf("(%s, id) %s (?, ?)", sortField, op)
|
||
|
|
return query.Where(condition, sortValue, id)
|
||
|
|
}
|
||
|
|
|
||
|
|
// applyOrder 应用排序
|
||
|
|
func (b *CursorBuilder) applyOrder(query *gorm.DB) *gorm.DB {
|
||
|
|
// 主排序字段
|
||
|
|
orderClause := b.order.String()
|
||
|
|
|
||
|
|
// 添加 ID 作为第二排序字段,确保排序稳定
|
||
|
|
if b.order.IsDescending() {
|
||
|
|
orderClause += ", id DESC"
|
||
|
|
} else {
|
||
|
|
orderClause += ", id ASC"
|
||
|
|
}
|
||
|
|
|
||
|
|
return query.Order(orderClause)
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetPageSize 获取页面大小
|
||
|
|
func (b *CursorBuilder) GetPageSize() int {
|
||
|
|
return b.pageSize
|
||
|
|
}
|
||
|
|
|
||
|
|
// HasMore 判断是否有更多数据
|
||
|
|
// items 是查询结果切片,需要传入指针切片
|
||
|
|
func HasMore(itemsLen int, pageSize int) bool {
|
||
|
|
return itemsLen > pageSize
|
||
|
|
}
|
||
|
|
|
||
|
|
// TrimItems 裁剪结果到实际页面大小
|
||
|
|
func TrimItems(itemsLen int, pageSize int) int {
|
||
|
|
if itemsLen > pageSize {
|
||
|
|
return pageSize
|
||
|
|
}
|
||
|
|
return itemsLen
|
||
|
|
}
|
||
|
|
|
||
|
|
// BuildResponse 从结果构建响应
|
||
|
|
// items: 结果切片(需要是切片类型)
|
||
|
|
// getSortValue: 获取排序字段值的函数
|
||
|
|
// getID: 获取ID的函数
|
||
|
|
func BuildResponse[T any](items []T, order SortOrder, pageSize int, getSortValue func(T) string, getID func(T) string) *PageResponse {
|
||
|
|
hasMore := len(items) > pageSize
|
||
|
|
|
||
|
|
// 裁剪到实际页面大小
|
||
|
|
if hasMore {
|
||
|
|
items = items[:pageSize]
|
||
|
|
}
|
||
|
|
|
||
|
|
var nextCursor, prevCursor string
|
||
|
|
|
||
|
|
// 生成下一页游标
|
||
|
|
if hasMore && len(items) > 0 {
|
||
|
|
lastItem := items[len(items)-1]
|
||
|
|
nextCursor = NewCursor(getSortValue(lastItem), getID(lastItem), order).Encode()
|
||
|
|
}
|
||
|
|
|
||
|
|
return &PageResponse{
|
||
|
|
Items: items,
|
||
|
|
NextCursor: nextCursor,
|
||
|
|
PrevCursor: prevCursor,
|
||
|
|
HasMore: hasMore,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// BuildResponseWithPrev 从结果构建响应(包含上一页游标)
|
||
|
|
func BuildResponseWithPrev[T any](items []T, order SortOrder, pageSize int, getSortValue func(T) string, getID func(T) string) *PageResponse {
|
||
|
|
hasMore := len(items) > pageSize
|
||
|
|
|
||
|
|
// 裁剪到实际页面大小
|
||
|
|
if hasMore {
|
||
|
|
items = items[:pageSize]
|
||
|
|
}
|
||
|
|
|
||
|
|
var nextCursor, prevCursor string
|
||
|
|
|
||
|
|
// 生成下一页游标
|
||
|
|
if hasMore && len(items) > 0 {
|
||
|
|
lastItem := items[len(items)-1]
|
||
|
|
nextCursor = NewCursor(getSortValue(lastItem), getID(lastItem), order).Encode()
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成上一页游标
|
||
|
|
if len(items) > 0 {
|
||
|
|
firstItem := items[0]
|
||
|
|
prevCursor = NewCursor(getSortValue(firstItem), getID(firstItem), order).Encode()
|
||
|
|
}
|
||
|
|
|
||
|
|
return &PageResponse{
|
||
|
|
Items: items,
|
||
|
|
NextCursor: nextCursor,
|
||
|
|
PrevCursor: prevCursor,
|
||
|
|
HasMore: hasMore,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// FormatTime 格式化时间为字符串(用于游标)
|
||
|
|
func FormatTime(t time.Time) string {
|
||
|
|
return t.Format(time.RFC3339Nano)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ParseTime 解析时间字符串(从游标)
|
||
|
|
func ParseTime(s string) (time.Time, error) {
|
||
|
|
return time.Parse(time.RFC3339Nano, s)
|
||
|
|
}
|