房间个人热度
This commit is contained in:
parent
1e038aa074
commit
f4a76d7b3f
@ -657,6 +657,7 @@ type RoomMicChanged struct {
|
||||
PublishEventTimeMs int64 `protobuf:"varint,10,opt,name=publish_event_time_ms,json=publishEventTimeMs,proto3" json:"publish_event_time_ms,omitempty"`
|
||||
MicMuted bool `protobuf:"varint,11,opt,name=mic_muted,json=micMuted,proto3" json:"mic_muted,omitempty"`
|
||||
SeatStatus string `protobuf:"bytes,12,opt,name=seat_status,json=seatStatus,proto3" json:"seat_status,omitempty"`
|
||||
TargetGiftValue int64 `protobuf:"varint,13,opt,name=target_gift_value,json=targetGiftValue,proto3" json:"target_gift_value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -775,6 +776,13 @@ func (x *RoomMicChanged) GetSeatStatus() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoomMicChanged) GetTargetGiftValue() int64 {
|
||||
if x != nil {
|
||||
return x.TargetGiftValue
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// RoomMicSeatLocked 表达单个麦位锁定状态变更。
|
||||
type RoomMicSeatLocked struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@ -1199,6 +1207,7 @@ type RoomGiftSent struct {
|
||||
GiftName string `protobuf:"bytes,15,opt,name=gift_name,json=giftName,proto3" json:"gift_name,omitempty"`
|
||||
GiftIconUrl string `protobuf:"bytes,16,opt,name=gift_icon_url,json=giftIconUrl,proto3" json:"gift_icon_url,omitempty"`
|
||||
GiftAnimationUrl string `protobuf:"bytes,17,opt,name=gift_animation_url,json=giftAnimationUrl,proto3" json:"gift_animation_url,omitempty"`
|
||||
TargetGiftValue int64 `protobuf:"varint,18,opt,name=target_gift_value,json=targetGiftValue,proto3" json:"target_gift_value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -1352,6 +1361,13 @@ func (x *RoomGiftSent) GetGiftAnimationUrl() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoomGiftSent) GetTargetGiftValue() int64 {
|
||||
if x != nil {
|
||||
return x.TargetGiftValue
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// RoomHeatChanged 表达热度变化结果。
|
||||
type RoomHeatChanged struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@ -2095,7 +2111,7 @@ const file_proto_events_room_v1_events_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"RoomClosed\x12\"\n" +
|
||||
"\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12\x16\n" +
|
||||
"\x06reason\x18\x02 \x01(\tR\x06reason\"\xac\x03\n" +
|
||||
"\x06reason\x18\x02 \x01(\tR\x06reason\"\xd8\x03\n" +
|
||||
"\x0eRoomMicChanged\x12\"\n" +
|
||||
"\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12$\n" +
|
||||
"\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x1b\n" +
|
||||
@ -2110,7 +2126,8 @@ const file_proto_events_room_v1_events_proto_rawDesc = "" +
|
||||
" \x01(\x03R\x12publishEventTimeMs\x12\x1b\n" +
|
||||
"\tmic_muted\x18\v \x01(\bR\bmicMuted\x12\x1f\n" +
|
||||
"\vseat_status\x18\f \x01(\tR\n" +
|
||||
"seatStatus\"h\n" +
|
||||
"seatStatus\x12*\n" +
|
||||
"\x11target_gift_value\x18\r \x01(\x03R\x0ftargetGiftValue\"h\n" +
|
||||
"\x11RoomMicSeatLocked\x12\"\n" +
|
||||
"\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12\x17\n" +
|
||||
"\aseat_no\x18\x02 \x01(\x05R\x06seatNo\x12\x16\n" +
|
||||
@ -2135,7 +2152,7 @@ const file_proto_events_room_v1_events_proto_rawDesc = "" +
|
||||
"\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"\\\n" +
|
||||
"\x10RoomUserUnbanned\x12\"\n" +
|
||||
"\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12$\n" +
|
||||
"\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"\xdd\x04\n" +
|
||||
"\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"\x89\x05\n" +
|
||||
"\fRoomGiftSent\x12$\n" +
|
||||
"\x0esender_user_id\x18\x01 \x01(\x03R\fsenderUserId\x12$\n" +
|
||||
"\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x17\n" +
|
||||
@ -2159,7 +2176,8 @@ const file_proto_events_room_v1_events_proto_rawDesc = "" +
|
||||
"\x10cp_relation_type\x18\x0e \x01(\tR\x0ecpRelationType\x12\x1b\n" +
|
||||
"\tgift_name\x18\x0f \x01(\tR\bgiftName\x12\"\n" +
|
||||
"\rgift_icon_url\x18\x10 \x01(\tR\vgiftIconUrl\x12,\n" +
|
||||
"\x12gift_animation_url\x18\x11 \x01(\tR\x10giftAnimationUrl\"J\n" +
|
||||
"\x12gift_animation_url\x18\x11 \x01(\tR\x10giftAnimationUrl\x12*\n" +
|
||||
"\x11target_gift_value\x18\x12 \x01(\x03R\x0ftargetGiftValue\"J\n" +
|
||||
"\x0fRoomHeatChanged\x12\x14\n" +
|
||||
"\x05delta\x18\x01 \x01(\x03R\x05delta\x12!\n" +
|
||||
"\fcurrent_heat\x18\x02 \x01(\x03R\vcurrentHeat\"_\n" +
|
||||
|
||||
@ -95,6 +95,7 @@ message RoomMicChanged {
|
||||
int64 publish_event_time_ms = 10;
|
||||
bool mic_muted = 11;
|
||||
string seat_status = 12;
|
||||
int64 target_gift_value = 13;
|
||||
}
|
||||
|
||||
// RoomMicSeatLocked 表达单个麦位锁定状态变更。
|
||||
@ -163,6 +164,7 @@ message RoomGiftSent {
|
||||
string gift_name = 15;
|
||||
string gift_icon_url = 16;
|
||||
string gift_animation_url = 17;
|
||||
int64 target_gift_value = 18;
|
||||
}
|
||||
|
||||
// RoomHeatChanged 表达热度变化结果。
|
||||
|
||||
@ -193,6 +193,7 @@ type RoomUser struct {
|
||||
Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"`
|
||||
JoinedAtMs int64 `protobuf:"varint,3,opt,name=joined_at_ms,json=joinedAtMs,proto3" json:"joined_at_ms,omitempty"`
|
||||
LastSeenAtMs int64 `protobuf:"varint,4,opt,name=last_seen_at_ms,json=lastSeenAtMs,proto3" json:"last_seen_at_ms,omitempty"`
|
||||
GiftValue int64 `protobuf:"varint,5,opt,name=gift_value,json=giftValue,proto3" json:"gift_value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -255,6 +256,13 @@ func (x *RoomUser) GetLastSeenAtMs() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RoomUser) GetGiftValue() int64 {
|
||||
if x != nil {
|
||||
return x.GiftValue
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// RoomOnlineUser 是在线用户列表的展示读模型。
|
||||
// role 保留进房 presence 语义;room_role 只表达房间管理员身份;is_owner 单独表达房主身份。
|
||||
type RoomOnlineUser struct {
|
||||
@ -371,8 +379,10 @@ type SeatState struct {
|
||||
SeatStatus string `protobuf:"bytes,10,opt,name=seat_status,json=seatStatus,proto3" json:"seat_status,omitempty"`
|
||||
// mic_heartbeat_at_ms 是当前 mic_session 最近一次服务端接受麦上心跳的 UTC epoch ms。
|
||||
MicHeartbeatAtMs int64 `protobuf:"varint,11,opt,name=mic_heartbeat_at_ms,json=micHeartbeatAtMs,proto3" json:"mic_heartbeat_at_ms,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// gift_value 是当前房间内该麦位用户收到的礼物热度累计,用户离房后清零。
|
||||
GiftValue int64 `protobuf:"varint,12,opt,name=gift_value,json=giftValue,proto3" json:"gift_value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SeatState) Reset() {
|
||||
@ -482,6 +492,13 @@ func (x *SeatState) GetMicHeartbeatAtMs() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SeatState) GetGiftValue() int64 {
|
||||
if x != nil {
|
||||
return x.GiftValue
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// RankItem 表达房间内本地礼物榜项目。
|
||||
type RankItem struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@ -9371,13 +9388,15 @@ const file_proto_room_v1_room_proto_rawDesc = "" +
|
||||
"\rCommandResult\x12\x18\n" +
|
||||
"\aapplied\x18\x01 \x01(\bR\aapplied\x12!\n" +
|
||||
"\froom_version\x18\x02 \x01(\x03R\vroomVersion\x12$\n" +
|
||||
"\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\x80\x01\n" +
|
||||
"\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\x9f\x01\n" +
|
||||
"\bRoomUser\x12\x17\n" +
|
||||
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" +
|
||||
"\x04role\x18\x02 \x01(\tR\x04role\x12 \n" +
|
||||
"\fjoined_at_ms\x18\x03 \x01(\x03R\n" +
|
||||
"joinedAtMs\x12%\n" +
|
||||
"\x0flast_seen_at_ms\x18\x04 \x01(\x03R\flastSeenAtMs\"\xdd\x01\n" +
|
||||
"\x0flast_seen_at_ms\x18\x04 \x01(\x03R\flastSeenAtMs\x12\x1d\n" +
|
||||
"\n" +
|
||||
"gift_value\x18\x05 \x01(\x03R\tgiftValue\"\xdd\x01\n" +
|
||||
"\x0eRoomOnlineUser\x12\x17\n" +
|
||||
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" +
|
||||
"\x04role\x18\x02 \x01(\tR\x04role\x12\x1b\n" +
|
||||
@ -9387,7 +9406,7 @@ const file_proto_room_v1_room_proto_rawDesc = "" +
|
||||
"\fjoined_at_ms\x18\x05 \x01(\x03R\n" +
|
||||
"joinedAtMs\x12%\n" +
|
||||
"\x0flast_seen_at_ms\x18\x06 \x01(\x03R\flastSeenAtMs\x12\x19\n" +
|
||||
"\bis_owner\x18\a \x01(\bR\aisOwner\"\xb2\x03\n" +
|
||||
"\bis_owner\x18\a \x01(\bR\aisOwner\"\xd1\x03\n" +
|
||||
"\tSeatState\x12\x17\n" +
|
||||
"\aseat_no\x18\x01 \x01(\x05R\x06seatNo\x12\x17\n" +
|
||||
"\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x16\n" +
|
||||
@ -9401,7 +9420,9 @@ const file_proto_room_v1_room_proto_rawDesc = "" +
|
||||
"\vseat_status\x18\n" +
|
||||
" \x01(\tR\n" +
|
||||
"seatStatus\x12-\n" +
|
||||
"\x13mic_heartbeat_at_ms\x18\v \x01(\x03R\x10micHeartbeatAtMs\"|\n" +
|
||||
"\x13mic_heartbeat_at_ms\x18\v \x01(\x03R\x10micHeartbeatAtMs\x12\x1d\n" +
|
||||
"\n" +
|
||||
"gift_value\x18\f \x01(\x03R\tgiftValue\"|\n" +
|
||||
"\bRankItem\x12\x17\n" +
|
||||
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x14\n" +
|
||||
"\x05score\x18\x02 \x01(\x03R\x05score\x12\x1d\n" +
|
||||
|
||||
@ -32,6 +32,7 @@ message RoomUser {
|
||||
string role = 2;
|
||||
int64 joined_at_ms = 3;
|
||||
int64 last_seen_at_ms = 4;
|
||||
int64 gift_value = 5;
|
||||
}
|
||||
|
||||
// RoomOnlineUser 是在线用户列表的展示读模型。
|
||||
@ -67,6 +68,8 @@ message SeatState {
|
||||
string seat_status = 10;
|
||||
// mic_heartbeat_at_ms 是当前 mic_session 最近一次服务端接受麦上心跳的 UTC epoch ms。
|
||||
int64 mic_heartbeat_at_ms = 11;
|
||||
// gift_value 是当前房间内该麦位用户收到的礼物热度累计,用户离房后清零。
|
||||
int64 gift_value = 12;
|
||||
}
|
||||
|
||||
// RankItem 表达房间内本地礼物榜项目。
|
||||
|
||||
@ -83,6 +83,21 @@ func TestCreateRoomDataIncludesExplicitIMGroupID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomSeatDataIncludesGiftValue(t *testing.T) {
|
||||
seats := roomSeatDataFromSnapshot(&roomv1.RoomSnapshot{
|
||||
RoomId: "room-seat-gift-value",
|
||||
MicSeats: []*roomv1.SeatState{{
|
||||
SeatNo: 3,
|
||||
UserId: 1003,
|
||||
GiftValue: 367,
|
||||
}},
|
||||
})
|
||||
|
||||
if len(seats) != 1 || seats[0].SeatNo != 3 || seats[0].UserID != "1003" || seats[0].GiftValue != 367 {
|
||||
t.Fatalf("seat data must expose gift_value: %+v", seats)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomProfileAgeFromBirth(t *testing.T) {
|
||||
now := time.Date(2026, time.May, 27, 12, 0, 0, 0, time.UTC)
|
||||
tests := []struct {
|
||||
|
||||
@ -407,6 +407,7 @@ type roomSeatData struct {
|
||||
MicMuted bool `json:"mic_muted"`
|
||||
SeatStatus string `json:"seat_status,omitempty"`
|
||||
MicHeartbeatAtMS int64 `json:"mic_heartbeat_at_ms,omitempty"`
|
||||
GiftValue int64 `json:"gift_value"`
|
||||
}
|
||||
|
||||
type roomRankItemData struct {
|
||||
@ -842,6 +843,7 @@ func roomSeatDataFromSnapshot(snapshot *roomv1.RoomSnapshot) []roomSeatData {
|
||||
MicMuted: seat.GetMicMuted(),
|
||||
SeatStatus: seat.GetSeatStatus(),
|
||||
MicHeartbeatAtMS: seat.GetMicHeartbeatAtMs(),
|
||||
GiftValue: seat.GetGiftValue(),
|
||||
})
|
||||
}
|
||||
return items
|
||||
|
||||
@ -177,6 +177,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room
|
||||
"publish_event_time_ms": fmt.Sprintf("%d", body.GetPublishEventTimeMs()),
|
||||
"mic_muted": fmt.Sprintf("%t", body.GetMicMuted()),
|
||||
"seat_status": body.GetSeatStatus(),
|
||||
"target_gift_value": fmt.Sprintf("%d", body.GetTargetGiftValue()),
|
||||
}
|
||||
return base, true, nil
|
||||
case "RoomMicSeatLocked":
|
||||
@ -284,6 +285,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room
|
||||
"gift_count": fmt.Sprintf("%d", body.GetGiftCount()),
|
||||
"coin_spent": fmt.Sprintf("%d", body.GetCoinSpent()),
|
||||
"billing_receipt_id": body.GetBillingReceiptId(),
|
||||
"target_gift_value": fmt.Sprintf("%d", body.GetTargetGiftValue()),
|
||||
}
|
||||
return base, true, nil
|
||||
case "RoomRocketFuelChanged":
|
||||
|
||||
@ -88,6 +88,56 @@ func TestRoomBackgroundChangedPublishesDedicatedIMEvent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomGiftSentCarriesTargetGiftValueInIMAttributes(t *testing.T) {
|
||||
record, err := outbox.Build("room-gift-value", "RoomGiftSent", 8, time.Now(), &roomeventsv1.RoomGiftSent{
|
||||
SenderUserId: 1001,
|
||||
TargetUserId: 1002,
|
||||
GiftId: "gift_rose",
|
||||
GiftCount: 1,
|
||||
GiftValue: 117,
|
||||
TargetGiftValue: 367,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Build RoomGiftSent envelope failed: %v", err)
|
||||
}
|
||||
|
||||
event, publish, err := roomEventFromEnvelope(record.Envelope)
|
||||
if err != nil {
|
||||
t.Fatalf("RoomGiftSent should decode: %v", err)
|
||||
}
|
||||
if !publish {
|
||||
t.Fatal("RoomGiftSent must publish Tencent IM message")
|
||||
}
|
||||
if event.EventType != "room_gift_sent" || event.GiftValue != 117 || event.Attributes["target_gift_value"] != "367" {
|
||||
t.Fatalf("gift IM event target gift value mismatch: %+v", event)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomMicChangedCarriesTargetGiftValueInIMAttributes(t *testing.T) {
|
||||
record, err := outbox.Build("room-mic-value", "RoomMicChanged", 9, time.Now(), &roomeventsv1.RoomMicChanged{
|
||||
ActorUserId: 1001,
|
||||
TargetUserId: 1002,
|
||||
FromSeat: 2,
|
||||
ToSeat: 5,
|
||||
Action: "change",
|
||||
TargetGiftValue: 367,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Build RoomMicChanged envelope failed: %v", err)
|
||||
}
|
||||
|
||||
event, publish, err := roomEventFromEnvelope(record.Envelope)
|
||||
if err != nil {
|
||||
t.Fatalf("RoomMicChanged should decode: %v", err)
|
||||
}
|
||||
if !publish {
|
||||
t.Fatal("RoomMicChanged must publish Tencent IM message")
|
||||
}
|
||||
if event.EventType != "room_mic_changed" || event.SeatNo != 5 || event.Attributes["target_gift_value"] != "367" {
|
||||
t.Fatalf("mic IM event target gift value mismatch: %+v", event)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomPasswordChangedSkipsRoomGroupIM(t *testing.T) {
|
||||
record, err := outbox.Build("room-lock", "RoomPasswordChanged", 8, time.Now(), &roomeventsv1.RoomPasswordChanged{
|
||||
ActorUserId: 42,
|
||||
|
||||
@ -419,6 +419,8 @@ type SendGift struct {
|
||||
GiftPointAdded int64 `json:"gift_point_added,omitempty"`
|
||||
// HeatValue 是 Room Cell 恢复时唯一可信的热度增量,不能重新用客户端价格推导。
|
||||
HeatValue int64 `json:"heat_value,omitempty"`
|
||||
// TargetGiftValues 记录每个收礼用户本次从 wallet receipt 得到的热度增量;恢复时只按目标增量重建收礼用户在线态。
|
||||
TargetGiftValues map[int64]int64 `json:"target_gift_values,omitempty"`
|
||||
// PriceVersion 是本次钱包结算使用的服务端礼物价格版本。
|
||||
PriceVersion string `json:"price_version,omitempty"`
|
||||
// GiftTypeCode 是 wallet-service 结算时锁定的礼物类型,用于火箭礼物类型燃料规则。
|
||||
|
||||
@ -153,6 +153,26 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
|
||||
// 扣费成功后,Room Cell 同步更新热度和本地礼物榜。
|
||||
current.Heat += heatValue
|
||||
current.GiftRank = localRank.ApplyGift(cmd.ActorUserID(), heatValue, now)
|
||||
targetGiftValueDeltas := make(map[int64]int64, len(targetBillings))
|
||||
targetCurrentGiftValues := make(map[int64]int64, len(targetBillings))
|
||||
for _, targetBilling := range targetBillings {
|
||||
if targetBilling.Billing == nil || targetBilling.TargetUserID <= 0 {
|
||||
continue
|
||||
}
|
||||
targetGiftValue := targetBilling.Billing.GetHeatValue()
|
||||
if userState := current.OnlineUsers[targetBilling.TargetUserID]; userState != nil {
|
||||
// 收礼用户热度完全使用 wallet-service 已结算的 HeatValue,room-service 不重新判断普通/幸运/超级幸运比例。
|
||||
if targetGiftValue > 0 {
|
||||
userState.GiftValue += targetGiftValue
|
||||
targetGiftValueDeltas[targetBilling.TargetUserID] += targetGiftValue
|
||||
}
|
||||
targetCurrentGiftValues[targetBilling.TargetUserID] = userState.GiftValue
|
||||
}
|
||||
}
|
||||
if len(targetGiftValueDeltas) > 0 {
|
||||
// 命令日志只保存每个目标本次增量;累计值由在线用户态按 replay 顺序重建。
|
||||
settledCommand.TargetGiftValues = targetGiftValueDeltas
|
||||
}
|
||||
current.Version++
|
||||
rocketApply, err := s.applyRoomRocketGift(now, current, rocketConfig, cmd, &settledCommand, billing, roomMeta)
|
||||
if err != nil {
|
||||
@ -184,6 +204,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
|
||||
GiftName: targetBilling.Billing.GetGiftName(),
|
||||
GiftIconUrl: targetBilling.Billing.GetGiftIconUrl(),
|
||||
GiftAnimationUrl: targetBilling.Billing.GetGiftAnimationUrl(),
|
||||
TargetGiftValue: targetCurrentGiftValues[targetBilling.TargetUserID],
|
||||
})
|
||||
if err != nil {
|
||||
return mutationResult{}, nil, err
|
||||
@ -277,6 +298,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
|
||||
"gift_name": billing.GetGiftName(),
|
||||
"gift_icon_url": billing.GetGiftIconUrl(),
|
||||
"gift_animation_url": billing.GetGiftAnimationUrl(),
|
||||
"target_gift_value": fmt.Sprintf("%d", targetCurrentGiftValues[cmd.TargetUserID]),
|
||||
},
|
||||
},
|
||||
}, records, nil
|
||||
|
||||
@ -71,6 +71,7 @@ func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.
|
||||
current.MicSeats[index].MicHeartbeatAtMS = 0
|
||||
current.MicSeats[index].MicMuted = false
|
||||
seatStatus := current.MicSeats[index].SeatStatus()
|
||||
targetGiftValue := roomUserGiftValue(current, cmd.ActorUserID())
|
||||
|
||||
// RoomMicChanged 是房间外消费者和腾讯云 IM 共同使用的麦位事件源。
|
||||
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
|
||||
@ -83,6 +84,7 @@ func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.
|
||||
PublishState: state.MicPublishPending,
|
||||
PublishDeadlineMs: cmd.PublishDeadlineMS,
|
||||
SeatStatus: seatStatus,
|
||||
TargetGiftValue: targetGiftValue,
|
||||
})
|
||||
if err != nil {
|
||||
return mutationResult{}, nil, err
|
||||
@ -107,6 +109,7 @@ func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.
|
||||
"mic_session_id": cmd.MicSessionID,
|
||||
"publish_deadline_ms": int64String(cmd.PublishDeadlineMS),
|
||||
"seat_status": seatStatus,
|
||||
"target_gift_value": int64String(targetGiftValue),
|
||||
},
|
||||
},
|
||||
}, []outbox.Record{micEvent}, nil
|
||||
@ -296,6 +299,7 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe
|
||||
current.MicSeats[toIndex].MicMuted = movedSeat.MicMuted
|
||||
current.Version++
|
||||
seatStatus := current.MicSeats[toIndex].SeatStatus()
|
||||
targetGiftValue := roomUserGiftValue(current, cmd.TargetUserID)
|
||||
|
||||
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
|
||||
ActorUserId: cmd.ActorUserID(),
|
||||
@ -307,6 +311,7 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe
|
||||
PublishState: movedSeat.PublishState,
|
||||
PublishDeadlineMs: movedSeat.PublishDeadlineMS,
|
||||
SeatStatus: seatStatus,
|
||||
TargetGiftValue: targetGiftValue,
|
||||
})
|
||||
if err != nil {
|
||||
return mutationResult{}, nil, err
|
||||
@ -328,6 +333,7 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe
|
||||
"mic_session_id": movedSeat.MicSessionID,
|
||||
"publish_deadline_ms": int64String(movedSeat.PublishDeadlineMS),
|
||||
"seat_status": seatStatus,
|
||||
"target_gift_value": int64String(targetGiftValue),
|
||||
},
|
||||
},
|
||||
}, []outbox.Record{micEvent}, nil
|
||||
@ -401,6 +407,7 @@ func (s *Service) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmM
|
||||
}
|
||||
current.Version++
|
||||
seatStatus := current.MicSeats[index].SeatStatus()
|
||||
targetGiftValue := roomUserGiftValue(current, cmd.TargetUserID)
|
||||
|
||||
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
|
||||
ActorUserId: cmd.ActorUserID(),
|
||||
@ -414,6 +421,7 @@ func (s *Service) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmM
|
||||
PublishEventTimeMs: cmd.EventTimeMS,
|
||||
MicMuted: current.MicSeats[index].MicMuted,
|
||||
SeatStatus: seatStatus,
|
||||
TargetGiftValue: targetGiftValue,
|
||||
})
|
||||
if err != nil {
|
||||
return mutationResult{}, nil, err
|
||||
@ -441,6 +449,7 @@ func (s *Service) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmM
|
||||
"source": cmd.Source,
|
||||
"mic_muted": boolString(current.MicSeats[index].MicMuted),
|
||||
"seat_status": seatStatus,
|
||||
"target_gift_value": int64String(targetGiftValue),
|
||||
},
|
||||
},
|
||||
}, []outbox.Record{micEvent}, nil
|
||||
@ -565,6 +574,7 @@ func (s *Service) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest)
|
||||
current.MicSeats[index].MicMuted = cmd.Muted
|
||||
current.Version++
|
||||
seatStatus := current.MicSeats[index].SeatStatus()
|
||||
targetGiftValue := roomUserGiftValue(current, cmd.TargetUserID)
|
||||
|
||||
action := "mic_unmute"
|
||||
if cmd.Muted {
|
||||
@ -581,6 +591,7 @@ func (s *Service) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest)
|
||||
PublishDeadlineMs: seat.PublishDeadlineMS,
|
||||
MicMuted: cmd.Muted,
|
||||
SeatStatus: seatStatus,
|
||||
TargetGiftValue: targetGiftValue,
|
||||
})
|
||||
if err != nil {
|
||||
return mutationResult{}, nil, err
|
||||
@ -598,11 +609,12 @@ func (s *Service) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest)
|
||||
SeatNo: seat.SeatNo,
|
||||
RoomVersion: current.Version,
|
||||
Attributes: map[string]string{
|
||||
"action": action,
|
||||
"mic_session_id": seat.MicSessionID,
|
||||
"publish_state": seat.PublishState,
|
||||
"mic_muted": boolString(cmd.Muted),
|
||||
"seat_status": seatStatus,
|
||||
"action": action,
|
||||
"mic_session_id": seat.MicSessionID,
|
||||
"publish_state": seat.PublishState,
|
||||
"mic_muted": boolString(cmd.Muted),
|
||||
"seat_status": seatStatus,
|
||||
"target_gift_value": int64String(targetGiftValue),
|
||||
},
|
||||
},
|
||||
}, []outbox.Record{micEvent}, nil
|
||||
@ -654,3 +666,14 @@ func protoSeatByUser(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.SeatSt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func roomUserGiftValue(current *state.RoomState, userID int64) int64 {
|
||||
if current == nil || userID <= 0 {
|
||||
return 0
|
||||
}
|
||||
if userState := current.OnlineUsers[userID]; userState != nil {
|
||||
// 麦位事件只透出在线用户态中的收礼热度,避免把麦位号当成热度归属。
|
||||
return userState.GiftValue
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -379,6 +379,15 @@ func replay(current *state.RoomState, cmd command.Command, committedAtMS int64)
|
||||
// 送礼回放不能再次调用 wallet,只使用命令中记录的钱包结算热度重建展示态。
|
||||
total := typed.HeatValue
|
||||
current.Heat += total
|
||||
for targetUserID, giftValue := range sendGiftTargetGiftValues(typed) {
|
||||
if giftValue <= 0 {
|
||||
continue
|
||||
}
|
||||
if userState := current.OnlineUsers[targetUserID]; userState != nil {
|
||||
// 收礼用户热度只恢复当时仍在房间内的在线态;后续 Leave/Kick/Stale 命令会删除在线态并自然清零。
|
||||
userState.GiftValue += giftValue
|
||||
}
|
||||
}
|
||||
applied := false
|
||||
for index := range current.GiftRank {
|
||||
if current.GiftRank[index].UserID == typed.ActorUserID() {
|
||||
@ -417,6 +426,21 @@ func replay(current *state.RoomState, cmd command.Command, committedAtMS int64)
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendGiftTargetGiftValues(cmd *command.SendGift) map[int64]int64 {
|
||||
if cmd == nil {
|
||||
return nil
|
||||
}
|
||||
if len(cmd.TargetGiftValues) > 0 {
|
||||
return cmd.TargetGiftValues
|
||||
}
|
||||
if cmd.TargetUserID > 0 && len(cmd.TargetUserIDs) <= 1 && cmd.HeatValue > 0 {
|
||||
// 旧单目标命令没有 TargetGiftValues 时,HeatValue 同时就是该唯一收礼人的本次热度增量。
|
||||
return map[int64]int64{cmd.TargetUserID: cmd.HeatValue}
|
||||
}
|
||||
// 旧多目标命令无法从聚合 HeatValue 反推出每个收礼人的值,宁可不恢复也不能把总值错误扩散给每个目标。
|
||||
return nil
|
||||
}
|
||||
|
||||
func rocketStateFromSettledGift(cmd *command.SendGift) *state.RocketState {
|
||||
return &state.RocketState{
|
||||
CurrentLevel: cmd.RocketLevel,
|
||||
|
||||
@ -129,6 +129,12 @@ func TestSendGiftBatchDebitsMultipleTargets(t *testing.T) {
|
||||
if resp.GetRoomHeat() != 30 || resp.GetBillingReceiptId() != "receipt-202,receipt-303" {
|
||||
t.Fatalf("multi-target gift response must aggregate wallet results: %+v", resp)
|
||||
}
|
||||
if firstUser := onlineUserByID(resp.GetRoom(), firstTargetID); firstUser == nil || firstUser.GetGiftValue() != 11 {
|
||||
t.Fatalf("first target must receive only its own heat value: %+v", firstUser)
|
||||
}
|
||||
if secondUser := onlineUserByID(resp.GetRoom(), secondTargetID); secondUser == nil || secondUser.GetGiftValue() != 19 {
|
||||
t.Fatalf("second target must receive only its own heat value: %+v", secondUser)
|
||||
}
|
||||
if len(wallet.debitRequests) != 0 {
|
||||
t.Fatalf("multi-target gift must use wallet BatchDebitGift, single debits=%+v", wallet.debitRequests)
|
||||
}
|
||||
@ -150,9 +156,15 @@ func TestSendGiftBatchDebitsMultipleTargets(t *testing.T) {
|
||||
if got[firstTargetID].GetBillingReceiptId() != "receipt-202" || got[firstTargetID].GetGiftValue() != 11 || got[firstTargetID].GetCommandId() != "cmd-gift-multi:target:202" {
|
||||
t.Fatalf("first target GiftSent mismatch: %+v", got[firstTargetID])
|
||||
}
|
||||
if got[firstTargetID].GetTargetGiftValue() != 11 {
|
||||
t.Fatalf("first target GiftSent must carry its cumulative value: %+v", got[firstTargetID])
|
||||
}
|
||||
if got[secondTargetID].GetBillingReceiptId() != "receipt-303" || got[secondTargetID].GetGiftValue() != 19 || got[secondTargetID].GetCommandId() != "cmd-gift-multi:target:303" {
|
||||
t.Fatalf("second target GiftSent mismatch: %+v", got[secondTargetID])
|
||||
}
|
||||
if got[secondTargetID].GetTargetGiftValue() != 19 {
|
||||
t.Fatalf("second target GiftSent must carry its cumulative value: %+v", got[secondTargetID])
|
||||
}
|
||||
}
|
||||
|
||||
func (w *rocketTestWallet) GrantResourceGroup(_ context.Context, req *walletv1.GrantResourceGroupRequest) (*walletv1.ResourceGrantResponse, error) {
|
||||
|
||||
@ -0,0 +1,192 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
||||
roomv1 "hyapp.local/api/proto/room/v1"
|
||||
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||||
"hyapp/services/room-service/internal/integration"
|
||||
roomservice "hyapp/services/room-service/internal/room/service"
|
||||
"hyapp/services/room-service/internal/router"
|
||||
"hyapp/services/room-service/internal/testutil/mysqltest"
|
||||
)
|
||||
|
||||
func TestRoomUserGiftValueFollowsMicChangeAndResetsAfterLeave(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repository := mysqltest.NewRepository(t)
|
||||
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 8, 10, 0, 0, 0, time.UTC)}
|
||||
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{{
|
||||
BillingReceiptId: "receipt-target-heat",
|
||||
CoinSpent: 1000,
|
||||
ChargeAmount: 1000,
|
||||
HeatValue: 117,
|
||||
GiftTypeCode: "normal",
|
||||
}}}
|
||||
svc := newUserGiftValueTestService(repository, wallet, now, "node-user-gift-value")
|
||||
|
||||
roomID := "room-user-gift-value"
|
||||
ownerID := int64(61001)
|
||||
targetID := int64(61002)
|
||||
createRocketRoom(t, ctx, svc, roomID, ownerID, 9001)
|
||||
joinRocketRoom(t, ctx, svc, roomID, targetID)
|
||||
if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
|
||||
Meta: rocketMeta(roomID, targetID, "target-heat-up"),
|
||||
SeatNo: 2,
|
||||
}); err != nil {
|
||||
t.Fatalf("target mic up failed: %v", err)
|
||||
}
|
||||
|
||||
giftResp, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
|
||||
Meta: rocketMeta(roomID, ownerID, "target-heat-gift"),
|
||||
TargetType: "user",
|
||||
TargetUserId: targetID,
|
||||
GiftId: "gift-target-heat",
|
||||
GiftCount: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("send gift failed: %v", err)
|
||||
}
|
||||
if user := onlineUserByID(giftResp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 117 {
|
||||
t.Fatalf("target online user gift_value mismatch: %+v", user)
|
||||
}
|
||||
if seat := seatByNo(giftResp.GetRoom(), 2); seat == nil || seat.GetUserId() != targetID || seat.GetGiftValue() != 117 {
|
||||
t.Fatalf("target seat gift_value mismatch after gift: %+v", seat)
|
||||
}
|
||||
giftEvents := roomGiftSentEvents(t, ctx, repository)
|
||||
if len(giftEvents) != 1 || giftEvents[0].GetTargetGiftValue() != 117 {
|
||||
t.Fatalf("RoomGiftSent must carry target cumulative gift value: %+v", giftEvents)
|
||||
}
|
||||
|
||||
changeResp, err := svc.ChangeMicSeat(ctx, &roomv1.ChangeMicSeatRequest{
|
||||
Meta: rocketMeta(roomID, targetID, "target-heat-change"),
|
||||
TargetUserId: targetID,
|
||||
SeatNo: 5,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("change mic failed: %v", err)
|
||||
}
|
||||
if oldSeat := seatByNo(changeResp.GetRoom(), 2); oldSeat == nil || oldSeat.GetUserId() != 0 || oldSeat.GetGiftValue() != 0 {
|
||||
t.Fatalf("old seat must be empty after change: %+v", oldSeat)
|
||||
}
|
||||
if newSeat := seatByNo(changeResp.GetRoom(), 5); newSeat == nil || newSeat.GetUserId() != targetID || newSeat.GetGiftValue() != 117 {
|
||||
t.Fatalf("gift value must follow user to new seat: %+v", newSeat)
|
||||
}
|
||||
if changed := firstMicChangedEventByAction(t, ctx, repository, "change"); changed == nil || changed.GetTargetGiftValue() != 117 {
|
||||
t.Fatalf("RoomMicChanged/change must carry target gift value: %+v", changed)
|
||||
}
|
||||
|
||||
if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{Meta: rocketMeta(roomID, targetID, "target-heat-leave")}); err != nil {
|
||||
t.Fatalf("leave room failed: %v", err)
|
||||
}
|
||||
joinRocketRoom(t, ctx, svc, roomID, targetID)
|
||||
reupResp, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
|
||||
Meta: rocketMeta(roomID, targetID, "target-heat-reup"),
|
||||
SeatNo: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("target mic re-up failed: %v", err)
|
||||
}
|
||||
if user := onlineUserByID(reupResp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 0 {
|
||||
t.Fatalf("gift value must reset after leave and rejoin: %+v", user)
|
||||
}
|
||||
if seat := seatByNo(reupResp.GetRoom(), 1); seat == nil || seat.GetUserId() != targetID || seat.GetGiftValue() != 0 {
|
||||
t.Fatalf("rejoined user seat gift_value must be zero: %+v", seat)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomUserGiftValueRecoversFromCommandLog(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repository := mysqltest.NewRepository(t)
|
||||
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 8, 10, 30, 0, 0, time.UTC)}
|
||||
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{{
|
||||
BillingReceiptId: "receipt-replay-target-heat",
|
||||
CoinSpent: 1500,
|
||||
ChargeAmount: 1500,
|
||||
HeatValue: 367,
|
||||
GiftTypeCode: "lucky",
|
||||
}}}
|
||||
svc := newUserGiftValueTestService(repository, wallet, now, "node-user-gift-replay-writer")
|
||||
|
||||
roomID := "room-user-gift-replay"
|
||||
ownerID := int64(62001)
|
||||
targetID := int64(62002)
|
||||
createRocketRoom(t, ctx, svc, roomID, ownerID, 9001)
|
||||
joinRocketRoom(t, ctx, svc, roomID, targetID)
|
||||
if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
|
||||
Meta: rocketMeta(roomID, targetID, "replay-up"),
|
||||
SeatNo: 2,
|
||||
}); err != nil {
|
||||
t.Fatalf("target mic up failed: %v", err)
|
||||
}
|
||||
if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
|
||||
Meta: rocketMeta(roomID, ownerID, "replay-gift"),
|
||||
TargetType: "user",
|
||||
TargetUserId: targetID,
|
||||
GiftId: "gift-replay-target-heat",
|
||||
GiftCount: 1,
|
||||
}); err != nil {
|
||||
t.Fatalf("send gift failed: %v", err)
|
||||
}
|
||||
if _, err := svc.ChangeMicSeat(ctx, &roomv1.ChangeMicSeatRequest{
|
||||
Meta: rocketMeta(roomID, targetID, "replay-change"),
|
||||
TargetUserId: targetID,
|
||||
SeatNo: 4,
|
||||
}); err != nil {
|
||||
t.Fatalf("change mic failed: %v", err)
|
||||
}
|
||||
|
||||
recoveredSvc := newUserGiftValueTestService(repository, &rocketTestWallet{}, now, "node-user-gift-replay-reader")
|
||||
resp, err := recoveredSvc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{
|
||||
Meta: rocketMeta(roomID, ownerID, "replay-snapshot"),
|
||||
RoomId: roomID,
|
||||
ViewerUserId: ownerID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("recover room snapshot failed: %v", err)
|
||||
}
|
||||
if user := onlineUserByID(resp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 367 {
|
||||
t.Fatalf("recovered online user gift_value mismatch: %+v", user)
|
||||
}
|
||||
if oldSeat := seatByNo(resp.GetRoom(), 2); oldSeat == nil || oldSeat.GetUserId() != 0 || oldSeat.GetGiftValue() != 0 {
|
||||
t.Fatalf("recovered old seat must stay empty: %+v", oldSeat)
|
||||
}
|
||||
if newSeat := seatByNo(resp.GetRoom(), 4); newSeat == nil || newSeat.GetUserId() != targetID || newSeat.GetGiftValue() != 367 {
|
||||
t.Fatalf("recovered seat must keep target gift value after change: %+v", newSeat)
|
||||
}
|
||||
}
|
||||
|
||||
func newUserGiftValueTestService(repository *mysqltest.Repository, wallet *rocketTestWallet, clock *fixedRoomRocketClock, nodeID string) *roomservice.Service {
|
||||
return roomservice.New(roomservice.Config{
|
||||
NodeID: nodeID,
|
||||
LeaseTTL: 10 * time.Second,
|
||||
RankLimit: 20,
|
||||
SnapshotEveryN: 100,
|
||||
Clock: clock,
|
||||
}, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
|
||||
}
|
||||
|
||||
func firstMicChangedEventByAction(t *testing.T, ctx context.Context, repository *mysqltest.Repository, action string) *roomeventsv1.RoomMicChanged {
|
||||
t.Helper()
|
||||
|
||||
records, err := repository.ListPendingOutbox(ctx, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("list pending outbox failed: %v", err)
|
||||
}
|
||||
for _, record := range records {
|
||||
if record.EventType != "RoomMicChanged" {
|
||||
continue
|
||||
}
|
||||
var event roomeventsv1.RoomMicChanged
|
||||
if err := proto.Unmarshal(record.Envelope.GetBody(), &event); err != nil {
|
||||
t.Fatalf("decode mic changed event failed: %v", err)
|
||||
}
|
||||
if event.GetAction() == action {
|
||||
return &event
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -100,6 +100,8 @@ type RoomUserState struct {
|
||||
JoinedAtMS int64
|
||||
// LastSeenAtMS 是最后一次房间业务操作时间,不等同于腾讯云 IM 心跳。
|
||||
LastSeenAtMS int64
|
||||
// GiftValue 是该用户本次在线停留期间在当前房间收到的礼物热度,离房删除 OnlineUsers 后自然清零。
|
||||
GiftValue int64
|
||||
}
|
||||
|
||||
// UserModerationState 保存房间内 ban/mute 这类治理状态的时间边界。
|
||||
@ -387,12 +389,20 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot {
|
||||
Role: userState.Role,
|
||||
JoinedAtMs: userState.JoinedAtMS,
|
||||
LastSeenAtMs: userState.LastSeenAtMS,
|
||||
GiftValue: userState.GiftValue,
|
||||
})
|
||||
}
|
||||
|
||||
seats := make([]*roomv1.SeatState, 0, len(s.MicSeats))
|
||||
|
||||
for _, seat := range s.MicSeats {
|
||||
seatGiftValue := int64(0)
|
||||
if seat.UserID > 0 {
|
||||
if userState, exists := s.OnlineUsers[seat.UserID]; exists && userState != nil {
|
||||
// 麦位展示值从在线用户态派生,切麦只移动 UserID,不会重置已收到的礼物热度。
|
||||
seatGiftValue = userState.GiftValue
|
||||
}
|
||||
}
|
||||
// 麦位顺序沿用内部切片顺序,即初始化的 SeatNo 升序和后续原位修改。
|
||||
seats = append(seats, &roomv1.SeatState{
|
||||
SeatNo: seat.SeatNo,
|
||||
@ -406,6 +416,7 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot {
|
||||
MicMuted: seat.MicMuted,
|
||||
SeatStatus: seat.SeatStatus(),
|
||||
MicHeartbeatAtMs: seat.MicHeartbeatAtMS,
|
||||
GiftValue: seatGiftValue,
|
||||
})
|
||||
}
|
||||
|
||||
@ -494,6 +505,7 @@ func FromProto(snapshot *roomv1.RoomSnapshot) *RoomState {
|
||||
Role: user.GetRole(),
|
||||
JoinedAtMS: user.GetJoinedAtMs(),
|
||||
LastSeenAtMS: user.GetLastSeenAtMs(),
|
||||
GiftValue: user.GetGiftValue(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user