253 lines
9.7 KiB
Go
253 lines
9.7 KiB
Go
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.Where("assets.id IN (SELECT asset_id FROM asset_likes WHERE star_id = ? AND created_at >= ? GROUP BY asset_id)", starID, 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.Where("assets.id IN (SELECT asset_id FROM asset_likes WHERE star_id = ? AND created_at >= ? GROUP BY asset_id)", starID, 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":
|
||
// 本月:关联 asset_likes 表,筛选本月有点赞的藏品
|
||
db = db.Where("assets.id IN (SELECT asset_id FROM asset_likes WHERE star_id = ? AND created_at >= ? GROUP BY asset_id)", starID, 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.Where("assets.id IN (SELECT asset_id FROM asset_likes WHERE star_id = ? AND created_at >= ? GROUP BY asset_id)", starID, 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
|
||
}
|