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:
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"carrot_bbs/internal/cache"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/pkg/gorse"
|
||||
"carrot_bbs/internal/pkg/hook"
|
||||
"carrot_bbs/internal/repository"
|
||||
@@ -355,3 +356,13 @@ func (s *CommentService) Unlike(ctx context.Context, commentID, userID string) e
|
||||
func (s *CommentService) IsLiked(ctx context.Context, commentID, userID string) bool {
|
||||
return s.commentRepo.IsLiked(commentID, userID)
|
||||
}
|
||||
|
||||
// GetCommentsByCursor 游标分页获取帖子评论
|
||||
func (s *CommentService) GetCommentsByCursor(ctx context.Context, postID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) {
|
||||
return s.commentRepo.GetCommentsByCursor(ctx, postID, req)
|
||||
}
|
||||
|
||||
// GetRepliesByCursor 游标分页获取根评论的回复
|
||||
func (s *CommentService) GetRepliesByCursor(ctx context.Context, rootID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Comment], error) {
|
||||
return s.commentRepo.GetRepliesByCursor(ctx, rootID, req)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"carrot_bbs/internal/cache"
|
||||
apperrors "carrot_bbs/internal/errors"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/pkg/sse"
|
||||
"carrot_bbs/internal/pkg/utils"
|
||||
"carrot_bbs/internal/repository"
|
||||
@@ -88,6 +90,12 @@ type GroupService interface {
|
||||
|
||||
// 获取成员信息
|
||||
GetMember(groupID string, userID string) (*model.GroupMember, error)
|
||||
|
||||
// 游标分页方法
|
||||
GetGroupsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Group], error)
|
||||
GetUserGroupsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Group], error)
|
||||
GetMembersByCursor(ctx context.Context, groupID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.GroupMember], error)
|
||||
GetAnnouncementsByCursor(ctx context.Context, groupID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.GroupAnnouncement], error)
|
||||
}
|
||||
|
||||
// GroupMembersResult 群组成员缓存结果
|
||||
@@ -1614,3 +1622,25 @@ func (s *groupService) IsGroupOwner(userID string, groupID string) bool {
|
||||
func (s *groupService) GetMember(groupID string, userID string) (*model.GroupMember, error) {
|
||||
return s.groupRepo.GetMember(groupID, userID)
|
||||
}
|
||||
|
||||
// ==================== 游标分页方法 ====================
|
||||
|
||||
// GetGroupsByCursor 游标分页获取群组列表
|
||||
func (s *groupService) GetGroupsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Group], error) {
|
||||
return s.groupRepo.GetGroupsByCursor(ctx, req)
|
||||
}
|
||||
|
||||
// GetUserGroupsByCursor 游标分页获取用户加入的群组列表
|
||||
func (s *groupService) GetUserGroupsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Group], error) {
|
||||
return s.groupRepo.GetUserGroupsByCursor(ctx, userID, req)
|
||||
}
|
||||
|
||||
// GetMembersByCursor 游标分页获取群成员列表
|
||||
func (s *groupService) GetMembersByCursor(ctx context.Context, groupID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.GroupMember], error) {
|
||||
return s.groupRepo.GetMembersByCursor(ctx, groupID, req)
|
||||
}
|
||||
|
||||
// GetAnnouncementsByCursor 游标分页获取群公告列表
|
||||
func (s *groupService) GetAnnouncementsByCursor(ctx context.Context, groupID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.GroupAnnouncement], error) {
|
||||
return s.groupRepo.GetAnnouncementsByCursor(ctx, groupID, req)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"carrot_bbs/internal/cache"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
"go.uber.org/zap"
|
||||
@@ -294,3 +295,13 @@ func (s *MessageService) InvalidateUserUnreadCache(userID, conversationID string
|
||||
cache.InvalidateUnreadConversation(s.baseCache, userID)
|
||||
s.conversationCache.InvalidateUnreadCount(userID, conversationID)
|
||||
}
|
||||
|
||||
// GetMessagesByCursor 游标分页获取会话消息
|
||||
func (s *MessageService) GetMessagesByCursor(ctx context.Context, conversationID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Message], error) {
|
||||
return s.messageRepo.GetMessagesByCursor(ctx, conversationID, req)
|
||||
}
|
||||
|
||||
// GetConversationsByCursor 游标分页获取用户会话列表
|
||||
func (s *MessageService) GetConversationsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Conversation], error) {
|
||||
return s.messageRepo.GetConversationsByCursor(ctx, userID, req)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"carrot_bbs/internal/cache"
|
||||
apperrors "carrot_bbs/internal/errors"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/repository"
|
||||
)
|
||||
|
||||
@@ -166,5 +167,10 @@ func (s *NotificationService) ClearAllNotifications(ctx context.Context, userID
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNotificationsByCursor 游标分页获取用户通知
|
||||
func (s *NotificationService) GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) {
|
||||
return s.notificationRepo.GetNotificationsByCursor(ctx, userID, unreadOnly, req)
|
||||
}
|
||||
|
||||
// 错误定义
|
||||
var ErrUnauthorizedNotification = apperrors.ErrUnauthorizedNotification
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"carrot_bbs/internal/cache"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/pkg/gorse"
|
||||
"carrot_bbs/internal/pkg/hook"
|
||||
"carrot_bbs/internal/repository"
|
||||
@@ -40,6 +41,11 @@ type PostService interface {
|
||||
GetFavorites(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
|
||||
// 游标分页方法
|
||||
ListByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
SearchByCursor(ctx context.Context, keyword string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
GetUserPostsByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
|
||||
// 关注和推荐
|
||||
GetFollowingPosts(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
GetHotPosts(ctx context.Context, page, pageSize int) ([]*model.Post, int64, error)
|
||||
@@ -760,3 +766,29 @@ func (s *postServiceImpl) DeletePostWithTransaction(ctx context.Context, postID
|
||||
return s.postRepo.DeleteWithContext(ctx, postID)
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 游标分页方法 ==========
|
||||
|
||||
// ListByCursor 游标分页获取帖子列表
|
||||
func (s *postServiceImpl) ListByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
// 规范化请求参数
|
||||
req.Normalize()
|
||||
|
||||
return s.postRepo.GetPostsByCursor(ctx, userID, includePending, req)
|
||||
}
|
||||
|
||||
// SearchByCursor 游标分页搜索帖子
|
||||
func (s *postServiceImpl) SearchByCursor(ctx context.Context, keyword string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
// 规范化请求参数
|
||||
req.Normalize()
|
||||
|
||||
return s.postRepo.SearchPostsByCursor(ctx, keyword, req)
|
||||
}
|
||||
|
||||
// GetUserPostsByCursor 游标分页获取用户帖子
|
||||
func (s *postServiceImpl) GetUserPostsByCursor(ctx context.Context, userID string, includePending bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
// 规范化请求参数
|
||||
req.Normalize()
|
||||
|
||||
return s.postRepo.GetUserPostsByCursor(ctx, userID, includePending, req)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user