package service import ( "context" "fmt" "google.golang.org/protobuf/proto" roomeventsv1 "hyapp.local/api/proto/events/room/v1" ) // syncRTCUserMuteFromOutbox 把持久化的房间禁言事实补偿到腾讯 RTC 媒体平面。 // 重试时必须读取 Room Cell 的最新禁言集合,而不能直接重放旧事件里的 muted;否则旧 mute // 在 newer unmute 之后重试会重新关掉用户音频,形成跨系统状态回退。 func (s *Service) syncRTCUserMuteFromOutbox(ctx context.Context, envelope *roomeventsv1.EventEnvelope) error { if s == nil || s.rtcUserAudioBlocker == nil || envelope == nil || envelope.GetEventType() != "RoomUserMuted" { return nil } var event roomeventsv1.RoomUserMuted if err := proto.Unmarshal(envelope.GetBody(), &event); err != nil { return fmt.Errorf("decode rtc mute outbox event: %w", err) } if event.GetTargetUserId() <= 0 || envelope.GetRoomId() == "" { return fmt.Errorf("rtc mute outbox event is incomplete") } snapshot, err := s.currentSnapshot(ctx, envelope.GetRoomId()) if err != nil { return err } if snapshot == nil { // 房间已被物理清理时 RTC 连接也不再存在;把历史禁言事件视为终态成功, // 避免一条不可执行的外部副作用永久阻塞同一 outbox worker。 return nil } blocked := containsID(snapshot.GetMuteUserIds(), event.GetTargetUserId()) return s.rtcUserAudioBlocker.SetUserAudioBlockedByStrRoomID(ctx, envelope.GetRoomId(), event.GetTargetUserId(), blocked) }