2026-04-25 01:18:30 +08:00

110 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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