feat(api): add tab-based post feed endpoints with cursor pagination
Add support for different post feed types via tab parameter: - follow: posts from followed users (requires authentication) - hot: trending posts using Gorse most_liked_weekly or database fallback - recommend: personalized recommendations with random offset for diversity - latest: default chronological feed The cursor pagination detection is also improved to check for parameter existence rather than non-empty string value.
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -45,6 +46,9 @@ type PostService interface {
|
||||
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)
|
||||
GetFollowingPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
GetHotPostsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
GetRecommendedPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error)
|
||||
|
||||
// 关注和推荐
|
||||
GetFollowingPosts(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
@@ -792,3 +796,126 @@ func (s *postServiceImpl) GetUserPostsByCursor(ctx context.Context, userID strin
|
||||
|
||||
return s.postRepo.GetUserPostsByCursor(ctx, userID, includePending, req)
|
||||
}
|
||||
|
||||
// GetFollowingPostsByCursor 游标分页获取关注用户的帖子
|
||||
func (s *postServiceImpl) GetFollowingPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
// 规范化请求参数
|
||||
req.Normalize()
|
||||
|
||||
return s.postRepo.GetFollowingPostsByCursor(ctx, userID, req)
|
||||
}
|
||||
|
||||
// GetHotPostsByCursor 游标分页获取热门帖子
|
||||
func (s *postServiceImpl) GetHotPostsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
// 规范化请求参数
|
||||
req.Normalize()
|
||||
|
||||
// 如果Gorse启用,使用自定义的非个性化推荐器
|
||||
if s.gorseClient.IsEnabled() {
|
||||
// 计算偏移量
|
||||
offset := 0
|
||||
if req.Cursor != "" {
|
||||
// 尝试解析游标作为偏移量
|
||||
if parsed, err := strconv.Atoi(req.Cursor); err == nil {
|
||||
offset = parsed
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 most_liked_weekly 推荐器获取周热门
|
||||
// 多取1条用于判断是否还有下一页
|
||||
itemIDs, err := s.gorseClient.GetNonPersonalized(ctx, "most_liked_weekly", req.PageSize+1, offset, "")
|
||||
if err != nil {
|
||||
log.Printf("[WARN] Gorse GetNonPersonalized failed: %v, fallback to database", err)
|
||||
return s.getHotPostsByCursorFromDB(ctx, req)
|
||||
}
|
||||
if len(itemIDs) > 0 {
|
||||
hasMore := len(itemIDs) > req.PageSize
|
||||
if hasMore {
|
||||
itemIDs = itemIDs[:req.PageSize]
|
||||
}
|
||||
posts, err := s.postRepo.GetByIDs(itemIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 下一页的游标是当前偏移量 + 已获取数量
|
||||
nextCursor := ""
|
||||
if hasMore {
|
||||
nextCursor = strconv.Itoa(offset + req.PageSize)
|
||||
}
|
||||
return &cursor.CursorPageResult[*model.Post]{
|
||||
Items: posts,
|
||||
NextCursor: nextCursor,
|
||||
PrevCursor: "",
|
||||
HasMore: hasMore,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 降级:从数据库获取
|
||||
return s.getHotPostsByCursorFromDB(ctx, req)
|
||||
}
|
||||
|
||||
// getHotPostsByCursorFromDB 从数据库游标分页获取热门帖子(降级路径)
|
||||
func (s *postServiceImpl) getHotPostsByCursorFromDB(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
return s.postRepo.GetHotPostsByCursor(ctx, req)
|
||||
}
|
||||
|
||||
// GetRecommendedPostsByCursor 游标分页获取推荐帖子
|
||||
func (s *postServiceImpl) GetRecommendedPostsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
|
||||
// 规范化请求参数
|
||||
req.Normalize()
|
||||
|
||||
// 如果Gorse未启用或用户未登录,降级为热门帖子
|
||||
if !s.gorseClient.IsEnabled() || userID == "" {
|
||||
return s.GetHotPostsByCursor(ctx, req)
|
||||
}
|
||||
|
||||
// 计算偏移量:第一页使用随机偏移量,增加推荐多样性
|
||||
var offset int
|
||||
if req.Cursor == "" {
|
||||
// 第一页随机偏移 0-50,让每次刷新看到不同的推荐内容
|
||||
offset = rand.Intn(51)
|
||||
} else {
|
||||
// 尝试解析游标作为偏移量
|
||||
if parsed, err := strconv.Atoi(req.Cursor); err == nil {
|
||||
offset = parsed
|
||||
}
|
||||
}
|
||||
|
||||
// 从Gorse获取推荐列表
|
||||
// 多取1条用于判断是否还有下一页
|
||||
itemIDs, err := s.gorseClient.GetRecommend(ctx, userID, req.PageSize+1, offset)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] Gorse recommendation failed: %v, fallback to hot posts", err)
|
||||
return s.GetHotPostsByCursor(ctx, req)
|
||||
}
|
||||
|
||||
// 如果没有推荐结果,降级为热门帖子
|
||||
if len(itemIDs) == 0 {
|
||||
return s.GetHotPostsByCursor(ctx, req)
|
||||
}
|
||||
|
||||
hasMore := len(itemIDs) > req.PageSize
|
||||
if hasMore {
|
||||
itemIDs = itemIDs[:req.PageSize]
|
||||
}
|
||||
|
||||
// 根据ID列表查询帖子详情
|
||||
posts, err := s.postRepo.GetByIDs(itemIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 下一页的游标是当前偏移量 + 已获取数量
|
||||
nextCursor := ""
|
||||
if hasMore {
|
||||
nextCursor = strconv.Itoa(offset + req.PageSize)
|
||||
}
|
||||
|
||||
return &cursor.CursorPageResult[*model.Post]{
|
||||
Items: posts,
|
||||
NextCursor: nextCursor,
|
||||
PrevCursor: "",
|
||||
HasMore: hasMore,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user