110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
// 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
|
||
}
|