topfans/backend/services/assetService/repository/ranking_repository.go
2026-04-24 18:04:55 +08:00

262 lines
10 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 用户表和粉丝档案表获取昵称和头像
// 使用 GROUP BY 去重,并用聚合函数获取 owner 信息
// is_original 是布尔值,需要转为 int 再取 MAX
db := r.db.Model(&models.Asset{}).
Select("assets.id as asset_id, assets.name as asset_name, assets.cover_url, assets.owner_uid, MAX(assets.like_count) as like_count, MAX(assets.is_original::int) as is_original, MAX(fp.nickname) as owner_nickname, MAX(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的展品
// occupier_star_id 表示展品所属的明星
db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id AND exhibitions.deleted_at IS NULL").
Where("exhibitions.expire_at > ?", now).
Where("exhibitions.occupier_star_id = ?", starID)
case "month":
// 本月:本月内展览过的藏品(包括已下架的,只要 expire_at 在本月内即可)
db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Where("exhibitions.expire_at >= ?", startOfMonth).
Where("exhibitions.occupier_star_id = ?", starID)
case "total":
// 全部:直接使用 assets 表的 like_count无需额外条件
}
// 统计总数(先查询 ID 列表再 Count避免 DISTINCT 干扰)
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 AND exhibitions.deleted_at IS NULL").
Where("exhibitions.expire_at > ?", now).
Where("exhibitions.occupier_star_id = ?", starID)
case "month":
// 本月:本月内展览过的藏品(包括已下架的)
countDB = countDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Where("exhibitions.expire_at >= ?", startOfMonth).
Where("exhibitions.occupier_star_id = ?", starID)
}
if err := countDB.Count(&total).Error; err != nil {
return nil, 0, err
}
// 查询列表(使用 GROUP BY 去重,按点赞数排序)
var results []*RankingItem
err := db.Group("assets.id").
Order("MAX(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 AND exhibitions.deleted_at IS NULL").
Where("exhibitions.expire_at > ?", now).
Where("exhibitions.occupier_star_id = ?", starID)
case "month":
// 本月:本月内展览过的藏品(包括已下架的)
db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Where("exhibitions.expire_at >= ?", startOfMonth).
Where("exhibitions.occupier_star_id = ?", starID)
}
// 获取用户在该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 AND exhibitions.deleted_at IS NULL").
Where("exhibitions.expire_at > ?", now).
Where("exhibitions.occupier_star_id = ?", starID)
case "month":
// 本月:本月内展览过的藏品(包括已下架的)
rankingDB = rankingDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Where("exhibitions.expire_at >= ?", startOfMonth).
Where("exhibitions.occupier_star_id = ?", starID)
}
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
}