// Package rank 维护 Room Cell 内部的房间本地礼物榜。 package rank import ( "sort" "time" "hyapp/services/room-service/internal/room/state" ) // LocalRank 在 Room Cell 内同步维护房间礼物榜。 type LocalRank struct { // items 按 user_id 聚合累计分数,只有 Room Cell 串行任务会修改它。 items map[int64]*state.RankItem // limit 控制 Top 返回的榜单长度,0 或负数表示不裁剪。 limit int } // New 初始化一个空的本地榜单。 func New(limit int) *LocalRank { // LocalRank 不自己加锁,因为它只在 Room Cell mailbox 内被串行访问。 return &LocalRank{ items: make(map[int64]*state.RankItem), limit: limit, } } // FromState 用快照中的榜单结果恢复本地榜索引。 func FromState(items []state.RankItem, limit int) *LocalRank { rank := New(limit) for _, item := range items { // 快照切片转 map 索引,后续送礼更新可以 O(1) 找到用户榜单项。 copied := item rank.items[item.UserID] = &copied } return rank } // Clone 返回当前榜单的独立副本。 func (r *LocalRank) Clone() *LocalRank { if r == nil { // nil 榜单通常只出现在防御性调用,保持 nil 语义。 return nil } cloned := New(r.limit) for userID, item := range r.items { // 深拷贝 RankItem,避免调用方通过返回值修改 Room Cell 内部榜单。 copied := *item cloned.items[userID] = &copied } return cloned } // ReplaceWith 用新榜单整体替换旧榜单。 func (r *LocalRank) ReplaceWith(next *LocalRank) { // 整体替换可以避免部分字段变更失败后留下旧引用。 r.items = next.Clone().items r.limit = next.limit } // ApplyGift 把礼物值累计到指定用户的榜单项上,并返回最新 top N。 func (r *LocalRank) ApplyGift(userID int64, giftValue int64, now time.Time) []state.RankItem { item, exists := r.items[userID] if !exists { // 首次送礼时创建榜单项,分数和礼物值都由本次礼物累计。 item = &state.RankItem{ UserID: userID, } r.items[userID] = item } // 当前版本 Score 与 GiftValue 同步增长,后续可以把 Score 替换成活动权重。 item.Score += giftValue item.GiftValue += giftValue item.UpdatedAtMS = now.UnixMilli() return r.Top() } // Top 返回当前榜单按分数排序后的前 N 项。 func (r *LocalRank) Top() []state.RankItem { // 返回值是值拷贝切片,调用方不能反向修改 LocalRank 内部 map。 items := make([]state.RankItem, 0, len(r.items)) for _, item := range r.items { items = append(items, *item) } sort.Slice(items, func(i int, j int) bool { if items[i].Score == items[j].Score { // 分数相同时最近更新靠前,符合房间内即时展示预期。 return items[i].UpdatedAtMS > items[j].UpdatedAtMS } return items[i].Score > items[j].Score }) if r.limit > 0 && len(items) > r.limit { items = items[:r.limit] } return items }