2026-05-16 04:09:08 +08:00

240 lines
7.5 KiB
Go

package voiceroomrocket
import (
"context"
"net/http"
"strconv"
"strings"
"time"
"chatapp3-golang/internal/integration"
"chatapp3-golang/internal/model"
)
// GetKingRanking 查询火箭王榜单。
func (s *Service) GetKingRanking(ctx context.Context, user AuthUser, roomID int64, level int, roundNo int, cursor int, limit int) (*KingResponse, error) {
if roomID <= 0 {
return nil, NewAppError(http.StatusBadRequest, "missing_room_id", "roomId is required")
}
if level <= 0 || level > defaultMaxLevel {
return nil, NewAppError(http.StatusBadRequest, "invalid_level", "level must be between 1 and 6")
}
cursor, limit = normalizePage(cursor, limit)
snapshot, err := s.loadConfig(ctx, user.SysOrigin)
if err != nil {
return nil, err
}
local := resolveLocation(snapshot.Timezone)
now := time.Now().In(local)
statusRow, err := s.loadOrInitStatus(ctx, nil, snapshot.SysOrigin, roomID, dayKey(now, local), now)
if err != nil {
return nil, err
}
if roundNo <= 0 {
roundNo = statusRow.RoundNo
}
sourceRoundNo := roundNo
records, err := s.rankRecords(ctx, snapshot.SysOrigin, roomID, statusRow.DayKey, sourceRoundNo, level, cursor, limit)
if err != nil {
return nil, err
}
if len(records) == 0 && roundNo == statusRow.RoundNo && level > statusRow.CurrentLevel && roundNo > 1 {
sourceRoundNo = roundNo - 1
records, err = s.rankRecords(ctx, snapshot.SysOrigin, roomID, statusRow.DayKey, sourceRoundNo, level, cursor, limit)
if err != nil {
return nil, err
}
}
resp := &KingResponse{
RoomID: roomID,
DayKey: statusRow.DayKey,
RoundNo: roundNo,
SourceRoundNo: sourceRoundNo,
Level: level,
Records: records,
}
if len(records) > 3 {
resp.Podium = append([]KingRecord(nil), records[:3]...)
} else {
resp.Podium = append([]KingRecord(nil), records...)
}
if len(records) == 0 {
resp.EmptyReason = "未达到火箭等级,没有火箭王"
}
return resp, nil
}
func (s *Service) topKingRecords(ctx context.Context, sysOrigin string, roomID int64, day string, roundNo int, level int, limit int) ([]KingRecord, error) {
if limit <= 0 {
limit = 3
}
return s.rankRecords(ctx, sysOrigin, roomID, day, roundNo, level, 1, limit)
}
func (s *Service) rankRecords(ctx context.Context, sysOrigin string, roomID int64, day string, roundNo int, level int, cursor int, limit int) ([]KingRecord, error) {
cursor, limit = normalizePage(cursor, limit)
var rows []model.VoiceRoomRocketContribution
if err := s.repo.DB.WithContext(ctx).
Where("sys_origin = ? AND room_id = ? AND day_key = ? AND round_no = ? AND level = ?", sysOrigin, roomID, day, roundNo, level).
Order("score_energy DESC, last_contribute_time ASC, user_id ASC").
Offset((cursor - 1) * limit).
Limit(limit).
Find(&rows).Error; err != nil {
return nil, err
}
profiles := s.mapUserProfiles(ctx, contributionUserIDs(rows))
records := make([]KingRecord, 0, len(rows))
baseRank := (cursor-1)*limit + 1
for index, row := range rows {
profile := profiles[row.UserID]
record := userProfileToKingRecord(row.UserID, baseRank+index, row.ScoreEnergy, profile)
if strings.TrimSpace(record.Account) == "" {
record.Account = strconv.FormatInt(row.UserID, 10)
}
if strings.TrimSpace(record.UserNickname) == "" {
record.UserNickname = record.Account
}
records = append(records, record)
}
return records, nil
}
func contributionUserIDs(rows []model.VoiceRoomRocketContribution) []int64 {
result := make([]int64, 0, len(rows))
seen := map[int64]struct{}{}
for _, row := range rows {
if row.UserID <= 0 {
continue
}
if _, ok := seen[row.UserID]; ok {
continue
}
seen[row.UserID] = struct{}{}
result = append(result, row.UserID)
}
return result
}
func (s *Service) mapUserProfiles(ctx context.Context, userIDs []int64) map[int64]integration.UserProfile {
result := make(map[int64]integration.UserProfile, len(userIDs))
if len(userIDs) == 0 {
return result
}
if s.repo.DB != nil {
var rows []model.UserBaseInfo
if err := s.repo.DB.WithContext(ctx).Where("id IN ?", userIDs).Find(&rows).Error; err == nil {
for _, row := range rows {
if row.ID <= 0 {
continue
}
result[row.ID] = userBaseInfoToProfile(row)
}
}
}
if s.userProfiles == nil {
return result
}
for _, userID := range userIDs {
current := result[userID]
if userProfileComplete(current) {
continue
}
profile, err := s.userProfiles.GetUserProfile(ctx, userID)
if err != nil {
if int64(current.ID) == 0 {
current.ID = integration.Int64Value(userID)
result[userID] = current
}
continue
}
result[userID] = mergeUserProfile(current, profile, userID)
}
return result
}
func userBaseInfoToProfile(row model.UserBaseInfo) integration.UserProfile {
return integration.UserProfile{
ID: integration.Int64Value(row.ID),
Account: strings.TrimSpace(row.Account),
UserAvatar: strings.TrimSpace(row.UserAvatar),
UserNickname: strings.TrimSpace(row.UserNickname),
OriginSys: strings.TrimSpace(row.OriginSys),
CountryCode: strings.TrimSpace(row.CountryCode),
CountryName: strings.TrimSpace(row.CountryName),
}
}
func userProfileComplete(profile integration.UserProfile) bool {
return int64(profile.ID) > 0 &&
strings.TrimSpace(profile.Account) != "" &&
strings.TrimSpace(profile.UserAvatar) != "" &&
strings.TrimSpace(profile.UserNickname) != "" &&
strings.TrimSpace(profile.CountryCode) != ""
}
func mergeUserProfile(base integration.UserProfile, fallback integration.UserProfile, userID int64) integration.UserProfile {
if int64(base.ID) == 0 {
if int64(fallback.ID) > 0 {
base.ID = fallback.ID
} else {
base.ID = integration.Int64Value(userID)
}
}
if strings.TrimSpace(base.Account) == "" {
base.Account = strings.TrimSpace(fallback.Account)
} else {
base.Account = strings.TrimSpace(base.Account)
}
if strings.TrimSpace(base.UserAvatar) == "" {
base.UserAvatar = strings.TrimSpace(fallback.UserAvatar)
} else {
base.UserAvatar = strings.TrimSpace(base.UserAvatar)
}
if strings.TrimSpace(base.UserNickname) == "" {
base.UserNickname = strings.TrimSpace(fallback.UserNickname)
} else {
base.UserNickname = strings.TrimSpace(base.UserNickname)
}
if int64(base.CountryID) == 0 {
base.CountryID = fallback.CountryID
}
if strings.TrimSpace(base.CountryCode) == "" {
base.CountryCode = strings.TrimSpace(fallback.CountryCode)
} else {
base.CountryCode = strings.TrimSpace(base.CountryCode)
}
if strings.TrimSpace(base.CountryName) == "" {
base.CountryName = strings.TrimSpace(fallback.CountryName)
} else {
base.CountryName = strings.TrimSpace(base.CountryName)
}
if strings.TrimSpace(base.RegionCode) == "" {
base.RegionCode = strings.TrimSpace(fallback.RegionCode)
} else {
base.RegionCode = strings.TrimSpace(base.RegionCode)
}
if strings.TrimSpace(base.OriginSys) == "" {
base.OriginSys = strings.TrimSpace(fallback.OriginSys)
} else {
base.OriginSys = strings.TrimSpace(base.OriginSys)
}
if strings.TrimSpace(base.AccountStatus) == "" {
base.AccountStatus = strings.TrimSpace(fallback.AccountStatus)
} else {
base.AccountStatus = strings.TrimSpace(base.AccountStatus)
}
return base
}
func rankRedisKey(sysOrigin string, roomID int64, day string, roundNo int, level int) string {
return "voice_room:rocket:rank:" + strings.ToUpper(strings.TrimSpace(sysOrigin)) +
":" + strconv.FormatInt(roomID, 10) +
":" + day +
":" + strconv.Itoa(roundNo) +
":" + strconv.Itoa(level)
}
func roomLockKey(sysOrigin string, roomID int64) string {
return "voice_room:rocket:lock:" + strings.ToUpper(strings.TrimSpace(sysOrigin)) + ":" + strconv.FormatInt(roomID, 10)
}