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) }