package repository import ( "errors" "time" "github.com/topfans/backend/pkg/models" "gorm.io/gorm" ) // RankingItem 排行榜项 type RankingItem struct { AssetID int64 AssetName string CoverURL string OwnerUID int64 OwnerNickname string OwnerAvatar *string LikeCount int32 IsOriginal bool } // RankingRepository 排行榜Repository接口 type RankingRepository interface { // GetHotRankingDisplaying 获取热度排行榜-展示中 GetHotRankingDisplaying(starID int64, limit, offset int) ([]*RankingItem, int64, error) // GetHotRankingMonth 获取热度排行榜-本月 GetHotRankingMonth(starID int64, limit, offset int) ([]*RankingItem, int64, error) // GetHotRankingTotal 获取热度排行榜-全部 GetHotRankingTotal(starID int64, limit, offset int) ([]*RankingItem, int64, error) // GetOriginalRankingDisplaying 获取自制排行榜-展示中 GetOriginalRankingDisplaying(starID int64, limit, offset int) ([]*RankingItem, int64, error) // GetOriginalRankingMonth 获取自制排行榜-本月 GetOriginalRankingMonth(starID int64, limit, offset int) ([]*RankingItem, int64, error) // GetOriginalRankingTotal 获取自制排行榜-全部 GetOriginalRankingTotal(starID int64, limit, offset int) ([]*RankingItem, int64, error) // GetMyBestRanking 获取用户最佳的排名 GetMyBestRanking(userID, starID int64, dimension string, isOriginalOnly bool) (*RankingItem, int32, error) } // rankingRepository 排行榜Repository实现 type rankingRepository struct { db *gorm.DB } // NewRankingRepository 创建排行榜Repository实例 func NewRankingRepository(db *gorm.DB) RankingRepository { return &rankingRepository{ db: db, } } // GetHotRankingDisplaying 获取热度排行榜-展示中 func (r *rankingRepository) GetHotRankingDisplaying(starID int64, limit, offset int) ([]*RankingItem, int64, error) { return r.getHotRankingByDimension(starID, "displaying", false, limit, offset) } // GetHotRankingMonth 获取热度排行榜-本月 func (r *rankingRepository) GetHotRankingMonth(starID int64, limit, offset int) ([]*RankingItem, int64, error) { return r.getHotRankingByDimension(starID, "month", false, limit, offset) } // GetHotRankingTotal 获取热度排行榜-全部 func (r *rankingRepository) GetHotRankingTotal(starID int64, limit, offset int) ([]*RankingItem, int64, error) { return r.getHotRankingByDimension(starID, "total", false, limit, offset) } // GetOriginalRankingDisplaying 获取自制排行榜-展示中 func (r *rankingRepository) GetOriginalRankingDisplaying(starID int64, limit, offset int) ([]*RankingItem, int64, error) { return r.getHotRankingByDimension(starID, "displaying", true, limit, offset) } // GetOriginalRankingMonth 获取自制排行榜-本月 func (r *rankingRepository) GetOriginalRankingMonth(starID int64, limit, offset int) ([]*RankingItem, int64, error) { return r.getHotRankingByDimension(starID, "month", true, limit, offset) } // GetOriginalRankingTotal 获取自制排行榜-全部 func (r *rankingRepository) GetOriginalRankingTotal(starID int64, limit, offset int) ([]*RankingItem, int64, error) { return r.getHotRankingByDimension(starID, "total", true, limit, offset) } // getHotRankingByDimension 根据维度获取排行榜 func (r *rankingRepository) getHotRankingByDimension(starID int64, dimension string, isOriginalOnly bool, limit, offset int) ([]*RankingItem, int64, error) { if starID <= 0 { return nil, 0, errors.New("star_id must be greater than 0") } if limit <= 0 { limit = 10 } now := time.Now().UnixMilli() startOfMonth := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Local).UnixMilli() // 构建基础查询,JOIN 用户表和粉丝档案表获取昵称和头像(一次查询,避免 N+1 问题) db := r.db.Model(&models.Asset{}). Select("assets.id as asset_id, assets.name as asset_name, assets.cover_url, assets.owner_uid, assets.like_count, assets.is_original, fp.nickname as owner_nickname, fp.avatar_url as owner_avatar"). Joins("LEFT JOIN fan_profiles fp ON fp.user_id = assets.owner_uid AND fp.star_id = ?", starID). Where("assets.star_id = ? AND assets.is_active = ? AND assets.status = ?", starID, true, models.AssetStatusActive) if isOriginalOnly { db = db.Where("assets.is_original = ?", true) } // 根据维度添加条件 switch dimension { case "displaying": // 展示中:关联 Exhibition 表,筛选未过期的,且是当前star的展馆 db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id"). Joins("INNER JOIN fan_profiles ON fan_profiles.id = exhibitions.host_profile_id"). Where("exhibitions.expire_at > ?", now). Where("fan_profiles.star_id = ?", starID) case "month": // 本月:本月内开始的展览,按点赞数排序 db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id"). Where("exhibitions.start_time >= ?", startOfMonth) case "total": // 全部:直接使用 assets 表的 like_count,无需额外条件 } // 统计总数 var total int64 countDB := r.db.Model(&models.Asset{}). Select("assets.id"). Joins("LEFT JOIN fan_profiles fp ON fp.user_id = assets.owner_uid AND fp.star_id = ?", starID). Where("assets.star_id = ? AND assets.is_active = ? AND assets.status = ?", starID, true, models.AssetStatusActive) if isOriginalOnly { countDB = countDB.Where("assets.is_original = ?", true) } switch dimension { case "displaying": countDB = countDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id"). Joins("INNER JOIN fan_profiles AS host_fp ON host_fp.id = exhibitions.host_profile_id"). Where("exhibitions.expire_at > ?", now). Where("host_fp.star_id = ?", starID) case "month": // 本月:本月内开始的展览 countDB = countDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id"). Where("exhibitions.start_time >= ?", startOfMonth) } if err := countDB.Count(&total).Error; err != nil { return nil, 0, err } // 查询列表 var results []*RankingItem err := db.Order("assets.like_count DESC, assets.id ASC"). Limit(limit). Offset(offset). Scan(&results).Error if err != nil { return nil, 0, err } return results, total, nil } // GetMyBestRanking 获取用户最佳的排名 func (r *rankingRepository) GetMyBestRanking(userID, starID int64, dimension string, isOriginalOnly bool) (*RankingItem, int32, error) { if userID <= 0 || starID <= 0 { return nil, 0, errors.New("user_id and star_id must be greater than 0") } now := time.Now().UnixMilli() startOfMonth := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Local).UnixMilli() // 构建基础查询,与 getHotRankingByDimension 保持一致 db := r.db.Model(&models.Asset{}). Select("assets.id as asset_id, assets.name as asset_name, assets.cover_url, assets.owner_uid, assets.like_count, assets.is_original, fp.nickname as owner_nickname, fp.avatar_url as owner_avatar"). Joins("LEFT JOIN fan_profiles fp ON fp.user_id = assets.owner_uid AND fp.star_id = ?", starID). Where("assets.owner_uid = ? AND assets.star_id = ? AND assets.is_active = ? AND assets.status = ?", userID, starID, true, models.AssetStatusActive) if isOriginalOnly { db = db.Where("assets.is_original = ?", true) } // 根据维度添加条件,与 getHotRankingByDimension 保持一致 switch dimension { case "displaying": // 展示中:关联 Exhibition 表,筛选未过期的,且是当前star的展馆 db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id"). Joins("INNER JOIN fan_profiles AS host_fp ON host_fp.id = exhibitions.host_profile_id"). Where("exhibitions.expire_at > ?", now). Where("host_fp.star_id = ?", starID) case "month": // 本月:本月内开始的展览 db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id"). Where("exhibitions.start_time >= ?", startOfMonth) } // 获取用户在该star下点赞数最高的藏品 var result struct { AssetID int64 AssetName string CoverURL string OwnerUID int64 LikeCount int32 IsOriginal bool OwnerNickname string OwnerAvatar *string } if err := db.Order("assets.like_count DESC, assets.id ASC").First(&result).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, 0, nil } return nil, 0, err } // 计算该藏品的排名(与 getHotRankingByDimension 的排序逻辑一致:like_count DESC, id ASC) var rank int64 rankingDB := r.db.Model(&models.Asset{}). Select("assets.id"). Joins("LEFT JOIN fan_profiles fp ON fp.user_id = assets.owner_uid AND fp.star_id = ?", starID). Where("assets.star_id = ? AND assets.is_active = ? AND assets.status = ?", starID, true, models.AssetStatusActive). Where("assets.like_count > ? OR (assets.like_count = ? AND assets.id < ?)", result.LikeCount, result.LikeCount, result.AssetID) if isOriginalOnly { rankingDB = rankingDB.Where("assets.is_original = ?", true) } switch dimension { case "displaying": rankingDB = rankingDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id"). Joins("INNER JOIN fan_profiles AS host_fp ON host_fp.id = exhibitions.host_profile_id"). Where("exhibitions.expire_at > ?", now). Where("host_fp.star_id = ?", starID) case "month": // 本月:本月内开始的展览 rankingDB = rankingDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id"). Where("exhibitions.start_time >= ?", startOfMonth) } if err := rankingDB.Count(&rank).Error; err != nil { return nil, 0, err } return &RankingItem{ AssetID: result.AssetID, AssetName: result.AssetName, CoverURL: result.CoverURL, OwnerUID: result.OwnerUID, LikeCount: result.LikeCount, IsOriginal: result.IsOriginal, OwnerNickname: result.OwnerNickname, OwnerAvatar: result.OwnerAvatar, }, int32(rank + 1), nil }