package voiceroomrocket import ( "context" "fmt" "sort" "strconv" "strings" "time" ) type roomAudienceUser struct { UserID int64 LastSeenTime time.Time } func (s *Service) listOnlineRoomUsers(ctx context.Context, sysOrigin string, roomID int64, now time.Time) ([]roomAudienceUser, error) { if s.repo.Redis == nil { return nil, nil } members, err := s.repo.Redis.SMembers(ctx, onlineRoomKey(sysOrigin, roomID)).Result() if err != nil { return nil, err } users := make([]roomAudienceUser, 0, len(members)) seen := make(map[int64]struct{}, len(members)) for _, raw := range members { userID, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64) if err != nil || userID <= 0 { continue } if _, exists := seen[userID]; exists { continue } seen[userID] = struct{}{} users = append(users, roomAudienceUser{ UserID: userID, LastSeenTime: s.loadUserSeenTime(ctx, sysOrigin, userID, now), }) } sort.Slice(users, func(i, j int) bool { return users[i].UserID < users[j].UserID }) return users, nil } func (s *Service) loadUserSeenTime(ctx context.Context, sysOrigin string, userID int64, fallback time.Time) time.Time { if s.repo.Redis == nil { return fallback } raw, err := s.repo.Redis.Get(ctx, userSeenKey(sysOrigin, userID)).Result() if err != nil { return fallback } millis, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64) if err != nil || millis <= 0 { return fallback } return resolveMillisTime(millis, fallback) } func onlineRoomKey(sysOrigin string, roomID int64) string { return fmt.Sprintf("voice_room:online:%s:%d", strings.ToUpper(strings.TrimSpace(sysOrigin)), roomID) } func userSeenKey(sysOrigin string, userID int64) string { return fmt.Sprintf("voice_room:user_seen:%s:%d", strings.ToUpper(strings.TrimSpace(sysOrigin)), userID) }