topfans/backend/services/assetService/repository/ranking_repository.go
2026-04-17 17:17:32 +08:00

259 lines
9.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}