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:
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
)
|
||||
@@ -251,3 +252,70 @@ func (h *CommentHandler) Unlike(c *gin.Context) {
|
||||
|
||||
response.SuccessWithMessage(c, "unliked", nil)
|
||||
}
|
||||
|
||||
// GetByPostIDByCursor 游标分页获取帖子评论
|
||||
func (h *CommentHandler) GetByPostIDByCursor(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
postID := c.Param("id")
|
||||
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.commentService.GetCommentsByCursor(c.Request.Context(), postID, req)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get comments")
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为响应结构,检查每个评论的点赞状态
|
||||
commentResponses := dto.ConvertCommentsToResponseWithUser(result.Items, userID, h.commentService)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.CommentCursorPageResponse{
|
||||
Items: commentResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// GetRepliesByRootIDByCursor 游标分页获取根评论的回复
|
||||
func (h *CommentHandler) GetRepliesByRootIDByCursor(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
rootID := c.Param("id")
|
||||
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.commentService.GetRepliesByCursor(c.Request.Context(), rootID, req)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get replies")
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为响应结构,检查每个回复的点赞状态
|
||||
replyResponses := dto.ConvertCommentsToResponseWithUser(result.Items, userID, h.commentService)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.CommentCursorPageResponse{
|
||||
Items: replyResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// parseCursorRequest 解析游标分页请求参数
|
||||
func (h *CommentHandler) parseCursorRequest(c *gin.Context) *cursor.PageRequest {
|
||||
cursorStr := c.Query("cursor")
|
||||
direction := cursor.Direction(c.DefaultQuery("direction", "forward"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
return cursor.NewPageRequest(cursorStr, direction, pageSize)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
)
|
||||
@@ -1279,6 +1280,163 @@ func (h *GroupHandler) DeleteAnnouncement(c *gin.Context) {
|
||||
response.SuccessWithMessage(c, "公告已删除", nil)
|
||||
}
|
||||
|
||||
// ==================== 游标分页方法 ====================
|
||||
|
||||
// GetGroupsByCursor 游标分页获取群组列表
|
||||
// GET /api/groups/cursor
|
||||
func (h *GroupHandler) GetGroupsByCursor(c *gin.Context) {
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.groupService.GetGroupsByCursor(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为响应结构
|
||||
groupResponses := dto.GroupsToResponseFromPtr(result.Items)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.GroupCursorPageResponse{
|
||||
Items: groupResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// GetUserGroupsByCursor 游标分页获取用户加入的群组列表
|
||||
// GET /api/users/:id/groups/cursor
|
||||
func (h *GroupHandler) GetUserGroupsByCursor(c *gin.Context) {
|
||||
userID := parseUserIDFromPath(c)
|
||||
if userID == "" {
|
||||
response.Unauthorized(c, "")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.groupService.GetUserGroupsByCursor(c.Request.Context(), userID, req)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为响应结构
|
||||
groupResponses := dto.GroupsToResponseFromPtr(result.Items)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.GroupCursorPageResponse{
|
||||
Items: groupResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// GetMembersByCursor 游标分页获取群成员列表
|
||||
// GET /api/groups/:id/members/cursor
|
||||
func (h *GroupHandler) GetMembersByCursor(c *gin.Context) {
|
||||
userID := parseUserID(c)
|
||||
if userID == "" {
|
||||
response.Unauthorized(c, "")
|
||||
return
|
||||
}
|
||||
|
||||
groupID := parseGroupID(c)
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "invalid group id")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.groupService.GetMembersByCursor(c.Request.Context(), groupID, req)
|
||||
if err != nil {
|
||||
if err == service.ErrGroupNotFound {
|
||||
response.NotFound(c, "群组不存在")
|
||||
return
|
||||
}
|
||||
response.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为响应结构
|
||||
memberResponses := dto.GroupMembersToResponseFromPtr(result.Items)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.GroupMemberCursorPageResponse{
|
||||
Items: memberResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// GetAnnouncementsByCursor 游标分页获取群公告列表
|
||||
// GET /api/groups/:id/announcements/cursor
|
||||
func (h *GroupHandler) GetAnnouncementsByCursor(c *gin.Context) {
|
||||
userID := parseUserID(c)
|
||||
if userID == "" {
|
||||
response.Unauthorized(c, "")
|
||||
return
|
||||
}
|
||||
|
||||
groupID := parseGroupID(c)
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "invalid group id")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.groupService.GetAnnouncementsByCursor(c.Request.Context(), groupID, req)
|
||||
if err != nil {
|
||||
if err == service.ErrGroupNotFound {
|
||||
response.NotFound(c, "群组不存在")
|
||||
return
|
||||
}
|
||||
response.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为响应结构
|
||||
announcementResponses := dto.GroupAnnouncementsToResponseFromPtr(result.Items)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.GroupAnnouncementCursorPageResponse{
|
||||
Items: announcementResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// parseCursorRequest 解析游标分页请求参数
|
||||
func (h *GroupHandler) parseCursorRequest(c *gin.Context) *cursor.PageRequest {
|
||||
cursorStr := c.Query("cursor")
|
||||
direction := cursor.Direction(c.DefaultQuery("direction", "forward"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
return cursor.NewPageRequest(cursorStr, direction, pageSize)
|
||||
}
|
||||
|
||||
// ==================== RESTful Action 端点 ====================
|
||||
|
||||
// HandleSetGroupKick 群组踢人
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/pkg/sse"
|
||||
"carrot_bbs/internal/service"
|
||||
@@ -975,3 +976,123 @@ func (h *MessageHandler) HandleSetConversationPinned(c *gin.Context) {
|
||||
"is_pinned": req.IsPinned,
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== Cursor Pagination Handlers ====================
|
||||
|
||||
// GetMessagesByCursor 游标分页获取会话消息
|
||||
// GET /api/v1/conversations/:id/messages/cursor
|
||||
// 请求参数:
|
||||
// - cursor: 游标字符串(可选)
|
||||
// - direction: 分页方向 forward 或 backward(默认 forward)
|
||||
// - page_size: 每页数量(默认 20,最大 100)
|
||||
func (h *MessageHandler) GetMessagesByCursor(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
response.Unauthorized(c, "")
|
||||
return
|
||||
}
|
||||
|
||||
conversationID := getIDParam(c, "id")
|
||||
if conversationID == "" {
|
||||
response.BadRequest(c, "conversation_id is required")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析游标分页参数
|
||||
pageReq := parseCursorPageRequest(c)
|
||||
|
||||
// 调用服务层
|
||||
result, err := h.messageService.GetMessagesByCursor(c.Request.Context(), conversationID, pageReq)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get messages")
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为响应格式
|
||||
items := dto.ConvertMessagesToResponse(result.Items)
|
||||
|
||||
response.Success(c, &dto.MessageCursorPageResponse{
|
||||
Items: items,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
})
|
||||
}
|
||||
|
||||
// GetConversationsByCursor 游标分页获取会话列表
|
||||
// GET /api/v1/conversations/cursor
|
||||
// 请求参数:
|
||||
// - cursor: 游标字符串(可选)
|
||||
// - direction: 分页方向 forward 或 backward(默认 forward)
|
||||
// - page_size: 每页数量(默认 20,最大 100)
|
||||
func (h *MessageHandler) GetConversationsByCursor(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
response.Unauthorized(c, "")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析游标分页参数
|
||||
pageReq := parseCursorPageRequest(c)
|
||||
|
||||
// 调用服务层
|
||||
result, err := h.messageService.GetConversationsByCursor(c.Request.Context(), userID, pageReq)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get conversations")
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤掉系统会话
|
||||
filteredItems := make([]*model.Conversation, 0)
|
||||
for _, conv := range result.Items {
|
||||
if conv.ID != model.SystemConversationID {
|
||||
filteredItems = append(filteredItems, conv)
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为响应格式
|
||||
items := make([]*dto.ConversationResponse, len(filteredItems))
|
||||
for i, conv := range filteredItems {
|
||||
// 获取未读数
|
||||
unreadCount, _ := h.chatService.GetUnreadCount(c.Request.Context(), conv.ID, userID)
|
||||
|
||||
// 获取最后一条消息
|
||||
var lastMessage *model.Message
|
||||
messages, _, _ := h.chatService.GetMessages(c.Request.Context(), conv.ID, userID, 1, 1)
|
||||
if len(messages) > 0 {
|
||||
lastMessage = messages[0]
|
||||
}
|
||||
|
||||
// 群聊时返回member_count,私聊时返回participants
|
||||
var resp *dto.ConversationResponse
|
||||
myParticipant, _ := h.getMyConversationParticipant(conv.ID, userID)
|
||||
isPinned := myParticipant != nil && myParticipant.IsPinned
|
||||
if conv.Type == model.ConversationTypeGroup && conv.GroupID != nil && *conv.GroupID != "" {
|
||||
// 群聊:实时计算群成员数量
|
||||
memberCount, _ := h.groupService.GetMemberCount(*conv.GroupID)
|
||||
resp = dto.ConvertConversationToResponse(conv, nil, int(unreadCount), lastMessage, isPinned)
|
||||
resp.MemberCount = memberCount
|
||||
} else {
|
||||
// 私聊:获取参与者信息
|
||||
participants, _ := h.getConversationParticipants(c.Request.Context(), conv.ID, userID)
|
||||
resp = dto.ConvertConversationToResponse(conv, participants, int(unreadCount), lastMessage, isPinned)
|
||||
}
|
||||
items[i] = resp
|
||||
}
|
||||
|
||||
response.Success(c, &dto.ConversationCursorPageResponse{
|
||||
Items: items,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
})
|
||||
}
|
||||
|
||||
// parseCursorPageRequest 解析游标分页请求参数
|
||||
func parseCursorPageRequest(c *gin.Context) *cursor.PageRequest {
|
||||
cursorStr := c.Query("cursor")
|
||||
direction := cursor.Direction(c.DefaultQuery("direction", "forward"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
return cursor.NewPageRequest(cursorStr, direction, pageSize)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
)
|
||||
@@ -130,3 +132,46 @@ func (h *NotificationHandler) ClearAllNotifications(c *gin.Context) {
|
||||
|
||||
response.Success(c, gin.H{"success": true})
|
||||
}
|
||||
|
||||
// GetNotificationsByCursor 游标分页获取通知列表
|
||||
func (h *NotificationHandler) GetNotificationsByCursor(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
response.Unauthorized(c, "")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
unreadOnly := c.Query("unread_only") == "true"
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.notificationService.GetNotificationsByCursor(c.Request.Context(), userID, unreadOnly, req)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get notifications")
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为响应结构
|
||||
notificationResponses := dto.ConvertNotificationsToResponse(result.Items)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.NotificationCursorPageResponse{
|
||||
Items: notificationResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// parseCursorRequest 解析游标分页请求参数
|
||||
func (h *NotificationHandler) parseCursorRequest(c *gin.Context) *cursor.PageRequest {
|
||||
cursorStr := c.Query("cursor")
|
||||
direction := cursor.Direction(c.DefaultQuery("direction", "forward"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
return cursor.NewPageRequest(cursorStr, direction, pageSize)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
)
|
||||
@@ -143,8 +144,16 @@ func (h *PostHandler) RecordView(c *gin.Context) {
|
||||
response.Success(c, gin.H{"success": true})
|
||||
}
|
||||
|
||||
// List 获取帖子列表
|
||||
// List 获取帖子列表(支持游标分页和偏移分页)
|
||||
func (h *PostHandler) List(c *gin.Context) {
|
||||
// 检查是否使用游标分页
|
||||
cursorStr := c.Query("cursor")
|
||||
if cursorStr != "" {
|
||||
h.ListByCursor(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 偏移分页(向后兼容)
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
page, pageSize = normalizePagination(page, pageSize)
|
||||
@@ -207,6 +216,45 @@ func (h *PostHandler) List(c *gin.Context) {
|
||||
response.Paginated(c, postResponses, total, page, pageSize)
|
||||
}
|
||||
|
||||
// ListByCursor 游标分页获取帖子列表
|
||||
func (h *PostHandler) ListByCursor(c *gin.Context) {
|
||||
userID := c.Query("user_id")
|
||||
currentUserID := c.GetString("user_id")
|
||||
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
// 判断是否包含待审核帖子
|
||||
includePending := userID != "" && userID == currentUserID
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.postService.ListByCursor(c.Request.Context(), userID, includePending, req)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get posts")
|
||||
return
|
||||
}
|
||||
|
||||
// 批量获取交互状态
|
||||
postIDs := make([]string, len(result.Items))
|
||||
for i, post := range result.Items {
|
||||
postIDs[i] = post.ID
|
||||
}
|
||||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||||
|
||||
// 转换为响应结构
|
||||
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, isLikedMap, isFavoritedMap)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.PostCursorPageResponse{
|
||||
Items: postResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// Update 更新帖子
|
||||
func (h *PostHandler) Update(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
@@ -412,8 +460,16 @@ func (h *PostHandler) Unfavorite(c *gin.Context) {
|
||||
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited))
|
||||
}
|
||||
|
||||
// GetUserPosts 获取用户帖子列表
|
||||
// GetUserPosts 获取用户帖子列表(支持游标分页和偏移分页)
|
||||
func (h *PostHandler) GetUserPosts(c *gin.Context) {
|
||||
// 检查是否使用游标分页
|
||||
cursorStr := c.Query("cursor")
|
||||
if cursorStr != "" {
|
||||
h.GetUserPostsByCursor(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 偏移分页(向后兼容)
|
||||
userID := c.Param("id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
@@ -440,6 +496,45 @@ func (h *PostHandler) GetUserPosts(c *gin.Context) {
|
||||
response.Paginated(c, postResponses, total, page, pageSize)
|
||||
}
|
||||
|
||||
// GetUserPostsByCursor 游标分页获取用户帖子列表
|
||||
func (h *PostHandler) GetUserPostsByCursor(c *gin.Context) {
|
||||
userID := c.Param("id")
|
||||
currentUserID := c.GetString("user_id")
|
||||
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
// 判断是否包含待审核帖子
|
||||
includePending := currentUserID != "" && currentUserID == userID
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.postService.GetUserPostsByCursor(c.Request.Context(), userID, includePending, req)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get user posts")
|
||||
return
|
||||
}
|
||||
|
||||
// 批量获取交互状态
|
||||
postIDs := make([]string, len(result.Items))
|
||||
for i, post := range result.Items {
|
||||
postIDs[i] = post.ID
|
||||
}
|
||||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||||
|
||||
// 转换为响应结构
|
||||
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, isLikedMap, isFavoritedMap)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.PostCursorPageResponse{
|
||||
Items: postResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// GetFavorites 获取收藏列表
|
||||
func (h *PostHandler) GetFavorites(c *gin.Context) {
|
||||
userID := c.Param("id")
|
||||
@@ -470,6 +565,15 @@ func (h *PostHandler) GetFavorites(c *gin.Context) {
|
||||
// Search 搜索帖子
|
||||
func (h *PostHandler) Search(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
|
||||
// 检查是否使用游标分页
|
||||
cursorStr := c.Query("cursor")
|
||||
if cursorStr != "" {
|
||||
h.SearchByCursor(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 偏移分页(向后兼容)
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
page, pageSize = normalizePagination(page, pageSize)
|
||||
@@ -493,3 +597,48 @@ func (h *PostHandler) Search(c *gin.Context) {
|
||||
|
||||
response.Paginated(c, postResponses, total, page, pageSize)
|
||||
}
|
||||
|
||||
// SearchByCursor 游标分页搜索帖子
|
||||
func (h *PostHandler) SearchByCursor(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
currentUserID := c.GetString("user_id")
|
||||
|
||||
// 解析游标分页请求
|
||||
req := h.parseCursorRequest(c)
|
||||
|
||||
// 调用游标分页服务
|
||||
result, err := h.postService.SearchByCursor(c.Request.Context(), keyword, req)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to search posts")
|
||||
return
|
||||
}
|
||||
|
||||
// 批量获取交互状态
|
||||
postIDs := make([]string, len(result.Items))
|
||||
for i, post := range result.Items {
|
||||
postIDs[i] = post.ID
|
||||
}
|
||||
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
|
||||
|
||||
// 转换为响应结构
|
||||
postResponses := dto.BuildPostsWithInteractionResponse(result.Items, isLikedMap, isFavoritedMap)
|
||||
|
||||
// 构建游标分页响应
|
||||
cursorResp := &dto.PostCursorPageResponse{
|
||||
Items: postResponses,
|
||||
NextCursor: result.NextCursor,
|
||||
PrevCursor: result.PrevCursor,
|
||||
HasMore: result.HasMore,
|
||||
}
|
||||
|
||||
response.Success(c, cursorResp)
|
||||
}
|
||||
|
||||
// parseCursorRequest 解析游标分页请求参数
|
||||
func (h *PostHandler) parseCursorRequest(c *gin.Context) *cursor.PageRequest {
|
||||
cursorStr := c.Query("cursor")
|
||||
direction := cursor.Direction(c.DefaultQuery("direction", "forward"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
return cursor.NewPageRequest(cursorStr, direction, pageSize)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user