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