725 lines
20 KiB
Go
725 lines
20 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/topfans/backend/pkg/database"
|
||
"github.com/topfans/backend/pkg/logger"
|
||
assetPb "github.com/topfans/backend/pkg/proto/asset"
|
||
pbCommon "github.com/topfans/backend/pkg/proto/common"
|
||
pb "github.com/topfans/backend/pkg/proto/social"
|
||
"github.com/topfans/backend/services/socialService/client"
|
||
"github.com/topfans/backend/services/socialService/repository"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// AssetLikeService 资产点赞业务逻辑层
|
||
type AssetLikeService struct {
|
||
assetClient *client.AssetClient
|
||
socialRepo repository.SocialRepository
|
||
}
|
||
|
||
// NewAssetLikeService 创建资产点赞Service实例
|
||
func NewAssetLikeService(assetClient *client.AssetClient, socialRepo repository.SocialRepository) *AssetLikeService {
|
||
return &AssetLikeService{
|
||
assetClient: assetClient,
|
||
socialRepo: socialRepo,
|
||
}
|
||
}
|
||
|
||
// LikeAsset 点赞资产
|
||
func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starID int64) (int32, error) {
|
||
logger.Logger.Debug("AssetLikeService.LikeAsset called",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
|
||
// 1. 验证资产是否存在(使用内部RPC,不验证所有权)
|
||
getAssetReq := &assetPb.GetAssetForRPCRequest{
|
||
AssetId: assetID,
|
||
}
|
||
getAssetResp, err := s.assetClient.GetAssetForRPC(ctx, getAssetReq)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get asset for RPC",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return 0, fmt.Errorf("获取藏品信息失败,请稍后重试")
|
||
}
|
||
|
||
if getAssetResp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||
logger.Logger.Warn("Asset not found or error",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int32("code", int32(getAssetResp.Base.Code)),
|
||
zap.String("message", getAssetResp.Base.Message),
|
||
)
|
||
return 0, fmt.Errorf("藏品不存在")
|
||
}
|
||
|
||
// 2. 检查是否已点赞
|
||
checkLikeReq := &assetPb.CheckAssetLikeRequest{
|
||
AssetId: assetID,
|
||
UserId: userID,
|
||
StarId: starID,
|
||
}
|
||
checkLikeResp, err := s.assetClient.CheckAssetLike(ctx, checkLikeReq)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to check asset like",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return 0, fmt.Errorf("点赞失败,请稍后重试")
|
||
}
|
||
|
||
if checkLikeResp.IsLiked {
|
||
logger.Logger.Warn("Already liked asset",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return 0, fmt.Errorf("当前展示已点赞")
|
||
}
|
||
|
||
// 3. 调用 Asset Service 点赞接口
|
||
likeReq := &assetPb.LikeAssetRequest{
|
||
AssetId: assetID,
|
||
}
|
||
likeResp, err := s.assetClient.LikeAsset(ctx, likeReq)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to like asset",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return 0, fmt.Errorf("点赞失败,请稍后重试")
|
||
}
|
||
|
||
if likeResp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||
logger.Logger.Warn("Like asset failed",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int32("code", int32(likeResp.Base.Code)),
|
||
zap.String("message", likeResp.Base.Message),
|
||
)
|
||
return 0, fmt.Errorf("点赞失败: %s", likeResp.Base.Message)
|
||
}
|
||
|
||
logger.Logger.Info("Successfully liked asset",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
zap.Int32("new_like_count", likeResp.LikeCount),
|
||
)
|
||
|
||
// 缓存失效
|
||
_ = database.InvalidateAssetLikersCache(ctx, assetID)
|
||
|
||
return likeResp.LikeCount, nil
|
||
}
|
||
|
||
// UnlikeAsset 取消点赞资产
|
||
func (s *AssetLikeService) UnlikeAsset(ctx context.Context, assetID, userID, starID int64) error {
|
||
logger.Logger.Debug("AssetLikeService.UnlikeAsset called",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
|
||
// 1. 检查是否已点赞
|
||
checkLikeReq := &assetPb.CheckAssetLikeRequest{
|
||
AssetId: assetID,
|
||
UserId: userID,
|
||
StarId: starID,
|
||
}
|
||
checkLikeResp, err := s.assetClient.CheckAssetLike(ctx, checkLikeReq)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to check asset like",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return fmt.Errorf("取消点赞失败,请稍后重试")
|
||
}
|
||
|
||
if !checkLikeResp.IsLiked {
|
||
logger.Logger.Warn("Not liked asset yet",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return fmt.Errorf("not liked this asset yet")
|
||
}
|
||
|
||
// 2. 调用 Asset Service 取消点赞接口
|
||
unlikeReq := &assetPb.UnlikeAssetRequest{
|
||
AssetId: assetID,
|
||
}
|
||
unlikeResp, err := s.assetClient.UnlikeAsset(ctx, unlikeReq)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to unlike asset",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return fmt.Errorf("取消点赞失败,请稍后重试")
|
||
}
|
||
|
||
if unlikeResp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||
logger.Logger.Warn("Unlike asset failed",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int32("code", int32(unlikeResp.Base.Code)),
|
||
zap.String("message", unlikeResp.Base.Message),
|
||
)
|
||
return fmt.Errorf("取消点赞失败: %s", unlikeResp.Base.Message)
|
||
}
|
||
|
||
logger.Logger.Info("Successfully unliked asset",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
|
||
// 缓存失效
|
||
_ = database.InvalidateAssetLikersCache(ctx, assetID)
|
||
|
||
return nil
|
||
}
|
||
|
||
// CheckAssetLike 检查是否已点赞资产
|
||
func (s *AssetLikeService) CheckAssetLike(ctx context.Context, assetID, userID, starID int64) (bool, error) {
|
||
logger.Logger.Debug("AssetLikeService.CheckAssetLike called",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
|
||
checkLikeReq := &assetPb.CheckAssetLikeRequest{
|
||
AssetId: assetID,
|
||
UserId: userID,
|
||
StarId: starID,
|
||
}
|
||
checkLikeResp, err := s.assetClient.CheckAssetLike(ctx, checkLikeReq)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to check asset like",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return false, fmt.Errorf("取消点赞失败,请稍后重试")
|
||
}
|
||
|
||
if checkLikeResp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||
logger.Logger.Warn("Check asset like failed",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int32("code", int32(checkLikeResp.Base.Code)),
|
||
zap.String("message", checkLikeResp.Base.Message),
|
||
)
|
||
return false, fmt.Errorf("查询点赞状态失败: %s", checkLikeResp.Base.Message)
|
||
}
|
||
|
||
return checkLikeResp.IsLiked, nil
|
||
}
|
||
|
||
// GetAssetLikes 获取资产点赞列表
|
||
func (s *AssetLikeService) GetAssetLikes(ctx context.Context, assetID int64, page, pageSize int32) (*assetPb.GetAssetLikesResponse, error) {
|
||
logger.Logger.Debug("AssetLikeService.GetAssetLikes called",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int32("page", page),
|
||
zap.Int32("page_size", pageSize),
|
||
)
|
||
|
||
getLikesReq := &assetPb.GetAssetLikesRequest{
|
||
AssetId: assetID,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
}
|
||
|
||
getLikesResp, err := s.assetClient.GetAssetLikes(ctx, getLikesReq)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get asset likes",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return nil, fmt.Errorf("获取点赞列表失败,请稍后重试")
|
||
}
|
||
|
||
if getLikesResp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||
logger.Logger.Warn("Get asset likes failed",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int32("code", int32(getLikesResp.Base.Code)),
|
||
zap.String("message", getLikesResp.Base.Message),
|
||
)
|
||
return nil, fmt.Errorf("获取点赞列表失败: %s", getLikesResp.Base.Message)
|
||
}
|
||
|
||
return getLikesResp, nil
|
||
}
|
||
|
||
// GetMyLikedAssets 获取我点赞的作品列表
|
||
func (s *AssetLikeService) GetMyLikedAssets(ctx context.Context, req *pb.GetMyLikedAssetsRequest, userID, starID int64) (*pb.GetMyLikedAssetsResponse, error) {
|
||
page := req.Page
|
||
if page <= 0 {
|
||
page = 1
|
||
}
|
||
pageSize := req.PageSize
|
||
if pageSize <= 0 {
|
||
pageSize = 20
|
||
}
|
||
if pageSize > 100 {
|
||
pageSize = 100
|
||
}
|
||
|
||
items, total, err := s.socialRepo.GetMyLikedAssets(userID, starID, int(page), int(pageSize), req.OrderBy)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get my liked assets",
|
||
zap.Error(err),
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return &pb.GetMyLikedAssetsResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR,
|
||
Message: "Failed to get my liked assets: " + err.Error(),
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// 提取 assetIDs 用于批量查询
|
||
assetIDs := make([]int64, 0, len(items))
|
||
likedAtMap := make(map[int64]int64)
|
||
for _, item := range items {
|
||
assetIDs = append(assetIDs, item.AssetID)
|
||
likedAtMap[item.AssetID] = item.LikedAt
|
||
}
|
||
|
||
// 批量获取排名
|
||
rankMap, _ := s.socialRepo.GetAssetRanks(assetIDs, starID)
|
||
|
||
// 批量获取过去1小时新增点赞
|
||
hourlyNewLikesMap, _ := s.socialRepo.GetHourlyNewLikes(assetIDs)
|
||
|
||
// 批量获取用户点赞后新增点赞数
|
||
userLikedCountAfterMap, _ := s.socialRepo.GetLikesCountAfterUserLiked(userID, assetIDs, likedAtMap)
|
||
|
||
// 计算 status_text 并转换为 proto 类型
|
||
pbItems := make([]*pb.LikedAssetItem, 0, len(items))
|
||
for _, item := range items {
|
||
statusText := computeStatusText(&statusTextInput{
|
||
UserLikedCountAfter: userLikedCountAfterMap[item.AssetID],
|
||
Rank: rankMap[item.AssetID],
|
||
HourlyNewLikes: hourlyNewLikesMap[item.AssetID],
|
||
IsExpired: item.ExpireAt > 0 && item.ExpireAt < time.Now().UnixMilli(),
|
||
})
|
||
|
||
pbItems = append(pbItems, &pb.LikedAssetItem{
|
||
AssetId: item.AssetID,
|
||
Name: item.Name,
|
||
CoverUrl: item.CoverURL,
|
||
LikeCount: item.LikeCount,
|
||
LikedAt: item.LikedAt,
|
||
Earnings: item.Earnings,
|
||
HourlyEarnings: item.HourlyEarnings,
|
||
IsLenticular: item.IsLenticular,
|
||
ExpireAt: item.ExpireAt,
|
||
StatusText: statusText,
|
||
})
|
||
}
|
||
|
||
hasMore := int64(page)*int64(pageSize) < total
|
||
|
||
return &pb.GetMyLikedAssetsResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "success",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
Data: &pb.LikedAssetsData{
|
||
Items: pbItems,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
Total: total,
|
||
HasMore: hasMore,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// statusTextInput status_text 计算输入
|
||
type statusTextInput struct {
|
||
UserLikedCountAfter int32 // 用户点赞后新增点赞数
|
||
Rank int // 排行榜排名
|
||
HourlyNewLikes int32 // 过去1小时新增点赞数
|
||
IsExpired bool // 是否已过期
|
||
}
|
||
|
||
// computeStatusText 计算 status_text 状态标签
|
||
func computeStatusText(item *statusTextInput) string {
|
||
// T0: 眼光拉满
|
||
if item.UserLikedCountAfter >= 50 && !item.IsExpired {
|
||
return "眼光拉满"
|
||
}
|
||
|
||
// T1: 排名型
|
||
switch {
|
||
case item.Rank >= 1 && item.Rank <= 5:
|
||
return fmt.Sprintf("屠榜顶流Top%d", item.Rank)
|
||
case item.Rank > 5 && item.Rank <= 10:
|
||
return fmt.Sprintf("第%d爆款", item.Rank)
|
||
case item.Rank == 20 || item.Rank == 50 || item.Rank == 100 || item.Rank == 200:
|
||
return fmt.Sprintf("排名破%d", item.Rank)
|
||
}
|
||
|
||
// T3/T4: 状态型
|
||
switch {
|
||
case item.HourlyNewLikes >= 20:
|
||
return "火速破圈"
|
||
case item.HourlyNewLikes >= 10:
|
||
return "小爆出圈"
|
||
case item.HourlyNewLikes >= 5:
|
||
return "热度积累"
|
||
case item.HourlyNewLikes >= 0:
|
||
return "缓慢涨粉"
|
||
default:
|
||
return "潜力待挖"
|
||
}
|
||
}
|
||
|
||
// GetMyTodayLikedAssets 获取我今日点赞的作品列表
|
||
func (s *AssetLikeService) GetMyTodayLikedAssets(ctx context.Context, req *pb.GetMyTodayLikedAssetsRequest, userID, starID int64) (*pb.GetMyTodayLikedAssetsResponse, error) {
|
||
page := req.Page
|
||
if page <= 0 {
|
||
page = 1
|
||
}
|
||
pageSize := req.PageSize
|
||
if pageSize <= 0 {
|
||
pageSize = 20
|
||
}
|
||
if pageSize > 100 {
|
||
pageSize = 100
|
||
}
|
||
|
||
items, total, err := s.socialRepo.GetMyTodayLikedAssets(userID, starID, int(page), int(pageSize))
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get my today liked assets",
|
||
zap.Error(err),
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return &pb.GetMyTodayLikedAssetsResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR,
|
||
Message: "Failed to get my today liked assets: " + err.Error(),
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
pbItems := make([]*pb.LikedAssetItem, 0, len(items))
|
||
for _, item := range items {
|
||
pbItems = append(pbItems, &pb.LikedAssetItem{
|
||
AssetId: item.AssetID,
|
||
Name: item.Name,
|
||
CoverUrl: item.CoverURL,
|
||
LikeCount: item.LikeCount,
|
||
LikedAt: item.LikedAt,
|
||
Earnings: item.Earnings,
|
||
HourlyEarnings: item.HourlyEarnings,
|
||
})
|
||
}
|
||
|
||
hasMore := int64(page)*int64(pageSize) < total
|
||
|
||
return &pb.GetMyTodayLikedAssetsResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "success",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
Data: &pb.LikedAssetsData{
|
||
Items: pbItems,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
Total: total,
|
||
HasMore: hasMore,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// GetMyWeekLikedAssets 获取我本周点赞的作品列表
|
||
func (s *AssetLikeService) GetMyWeekLikedAssets(ctx context.Context, req *pb.GetMyWeekLikedAssetsRequest, userID, starID int64) (*pb.GetMyWeekLikedAssetsResponse, error) {
|
||
page := req.Page
|
||
if page <= 0 {
|
||
page = 1
|
||
}
|
||
pageSize := req.PageSize
|
||
if pageSize <= 0 {
|
||
pageSize = 20
|
||
}
|
||
if pageSize > 100 {
|
||
pageSize = 100
|
||
}
|
||
|
||
items, total, err := s.socialRepo.GetMyWeekLikedAssets(userID, starID, int(page), int(pageSize))
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get my week liked assets",
|
||
zap.Error(err),
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return &pb.GetMyWeekLikedAssetsResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR,
|
||
Message: "Failed to get my week liked assets: " + err.Error(),
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
pbItems := make([]*pb.LikedAssetItem, 0, len(items))
|
||
for _, item := range items {
|
||
pbItems = append(pbItems, &pb.LikedAssetItem{
|
||
AssetId: item.AssetID,
|
||
Name: item.Name,
|
||
CoverUrl: item.CoverURL,
|
||
LikeCount: item.LikeCount,
|
||
LikedAt: item.LikedAt,
|
||
Earnings: item.Earnings,
|
||
HourlyEarnings: item.HourlyEarnings,
|
||
})
|
||
}
|
||
|
||
hasMore := int64(page)*int64(pageSize) < total
|
||
|
||
return &pb.GetMyWeekLikedAssetsResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "success",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
Data: &pb.LikedAssetsData{
|
||
Items: pbItems,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
Total: total,
|
||
HasMore: hasMore,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// GetUserLikedAssets 获取他人点赞的作品列表
|
||
func (s *AssetLikeService) GetUserLikedAssets(ctx context.Context, req *pb.GetUserLikedAssetsRequest, userID, starID int64) (*pb.GetUserLikedAssetsResponse, error) {
|
||
page := req.Page
|
||
if page <= 0 {
|
||
page = 1
|
||
}
|
||
pageSize := req.PageSize
|
||
if pageSize <= 0 {
|
||
pageSize = 20
|
||
}
|
||
if pageSize > 100 {
|
||
pageSize = 100
|
||
}
|
||
|
||
items, total, err := s.socialRepo.GetUserLikedAssets(userID, starID, int(page), int(pageSize))
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get user liked assets",
|
||
zap.Error(err),
|
||
zap.Int64("user_id", userID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
return &pb.GetUserLikedAssetsResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR,
|
||
Message: "Failed to get user liked assets: " + err.Error(),
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
pbItems := make([]*pb.LikedAssetItem, 0, len(items))
|
||
for _, item := range items {
|
||
pbItems = append(pbItems, &pb.LikedAssetItem{
|
||
AssetId: item.AssetID,
|
||
Name: item.Name,
|
||
CoverUrl: item.CoverURL,
|
||
LikeCount: item.LikeCount,
|
||
LikedAt: item.LikedAt,
|
||
Earnings: item.Earnings,
|
||
HourlyEarnings: item.HourlyEarnings,
|
||
})
|
||
}
|
||
|
||
hasMore := int64(page)*int64(pageSize) < total
|
||
|
||
return &pb.GetUserLikedAssetsResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "success",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
Data: &pb.LikedAssetsData{
|
||
Items: pbItems,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
Total: total,
|
||
HasMore: hasMore,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// GetAssetLikers 获取资产点赞用户列表(带缓存)
|
||
func (s *AssetLikeService) GetAssetLikers(ctx context.Context, assetID, starID int64, cursor int64, pageSize int32) (*pb.GetAssetLikersResponse, error) {
|
||
logger.Logger.Debug("AssetLikeService.GetAssetLikers called",
|
||
zap.Int64("asset_id", assetID),
|
||
zap.Int64("star_id", starID),
|
||
zap.Int64("cursor", cursor),
|
||
zap.Int32("page_size", pageSize),
|
||
)
|
||
|
||
// 参数校验
|
||
if pageSize <= 0 {
|
||
pageSize = 20
|
||
}
|
||
if pageSize > 100 {
|
||
pageSize = 100
|
||
}
|
||
|
||
// 0. 校验资产是否存在
|
||
getAssetReq := &assetPb.GetAssetForRPCRequest{AssetId: assetID}
|
||
getAssetResp, err := s.assetClient.GetAssetForRPC(ctx, getAssetReq)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get asset for RPC",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return &pb.GetAssetLikersResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR,
|
||
Message: "Failed to get asset",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
}, nil
|
||
}
|
||
if getAssetResp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||
logger.Logger.Warn("Asset not found",
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return &pb.GetAssetLikersResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_NOT_FOUND,
|
||
Message: "Asset not found",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// 1. 先查缓存
|
||
cache, err := database.GetAssetLikersCache(ctx, assetID)
|
||
if err != nil {
|
||
logger.Logger.Warn("Failed to get asset likers cache",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
// 缓存错误不影响主流程,继续查 DB
|
||
}
|
||
|
||
if cache != nil && len(cache.Users) > 0 {
|
||
// 缓存命中,从缓存中切片返回
|
||
return sliceFromCache(cache, cursor, pageSize)
|
||
}
|
||
|
||
// 2. 缓存未命中,查 DB
|
||
// 从已获取的资产信息中获取 starID
|
||
actualStarID := getAssetResp.StarId
|
||
if actualStarID == 0 {
|
||
actualStarID = starID // 兜底使用传入的 starID
|
||
}
|
||
|
||
// 查询数据库,最多缓存 1000 条
|
||
dbResults, err := s.socialRepo.GetByAssetWithUsers(assetID, actualStarID, 0, 1000)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get asset likers from DB",
|
||
zap.Error(err),
|
||
zap.Int64("asset_id", assetID),
|
||
)
|
||
return &pb.GetAssetLikersResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR,
|
||
Message: "Failed to get asset likers",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// 3. 写入缓存
|
||
total := int64(len(dbResults))
|
||
cacheUsers := make([]database.AssetLikerWithTotal, 0, len(dbResults))
|
||
for _, r := range dbResults {
|
||
cacheUsers = append(cacheUsers, database.AssetLikerWithTotal{
|
||
UserID: r.UserID,
|
||
Nickname: r.Nickname,
|
||
Avatar: r.Avatar,
|
||
FanLevel: r.FanLevel,
|
||
LikedAt: r.LikedAt,
|
||
StarID: r.StarID,
|
||
})
|
||
}
|
||
cache = &database.AssetLikersCache{
|
||
Users: cacheUsers,
|
||
Total: total,
|
||
UpdatedAt: time.Now().UnixMilli(),
|
||
}
|
||
_ = database.SetAssetLikersCache(ctx, assetID, cache, 60*time.Second)
|
||
|
||
// 4. 返回数据
|
||
return sliceFromCache(cache, cursor, pageSize)
|
||
}
|
||
|
||
// sliceFromCache 从缓存中按游标切片
|
||
func sliceFromCache(cache *database.AssetLikersCache, cursor int64, pageSize int32) (*pb.GetAssetLikersResponse, error) {
|
||
users := cache.Users
|
||
total := cache.Total
|
||
|
||
// 找到起始位置
|
||
start := 0
|
||
if cursor > 0 {
|
||
for i, u := range users {
|
||
if u.LikedAt < cursor {
|
||
start = i
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
// 找到结束位置
|
||
end := start + int(pageSize)
|
||
hasMore := end < len(users)
|
||
if end > len(users) {
|
||
end = len(users)
|
||
}
|
||
|
||
// 构建返回结果
|
||
pbUsers := make([]*pb.AssetLiker, 0, end-start)
|
||
var nextCursor int64
|
||
for i := start; i < end; i++ {
|
||
u := users[i]
|
||
pbUsers = append(pbUsers, &pb.AssetLiker{
|
||
UserId: u.UserID,
|
||
Nickname: u.Nickname,
|
||
Avatar: u.Avatar,
|
||
FanLevel: u.FanLevel,
|
||
LikedAt: u.LikedAt,
|
||
})
|
||
nextCursor = u.LikedAt
|
||
}
|
||
|
||
return &pb.GetAssetLikersResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "success",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
Users: pbUsers,
|
||
Total: total,
|
||
HasMore: hasMore,
|
||
NextCursor: nextCursor,
|
||
}, nil
|
||
}
|
||
|