From dd8dc489b71df6d1494997c5c3d1c8d83ec5c8b7 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 7 Jul 2026 20:09:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A7=E9=BA=A6=E4=BD=8D?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=81=A2=E5=A4=8D=E9=98=BB=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/room/service/recovery.go | 6 ++-- .../internal/room/service/recovery_test.go | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/services/room-service/internal/room/service/recovery.go b/services/room-service/internal/room/service/recovery.go index 289d9724..b39dbddc 100644 --- a/services/room-service/internal/room/service/recovery.go +++ b/services/room-service/internal/room/service/recovery.go @@ -327,7 +327,8 @@ func replay(current *state.RoomState, cmd command.Command, committedAtMS int64) case *command.ChangeMicSeat: from, exists := current.SeatByUser(typed.TargetUserID) if !exists { - return fmt.Errorf("change_mic_seat replay target is not on seat: %d", typed.TargetUserID) + // 换麦位属于麦上用户操作;历史过期命令若晚于下麦/离房恢复到达,当前状态已无源麦位时保持 no-op。 + return nil } fromIndex := current.SeatIndex(from.SeatNo) toIndex := current.SeatIndex(typed.SeatNo) @@ -353,7 +354,8 @@ func replay(current *state.RoomState, cmd command.Command, committedAtMS int64) case *command.SetMicMute: seat, exists := current.SeatByUser(typed.TargetUserID) if !exists { - return fmt.Errorf("set_mic_mute replay target is not on seat: %d", typed.TargetUserID) + // 静音状态只附着在当前麦位会话上;用户已不在麦上时,旧静音命令不能阻断房间恢复。 + return nil } index := current.SeatIndex(seat.SeatNo) current.MicSeats[index].MicMuted = typed.Muted diff --git a/services/room-service/internal/room/service/recovery_test.go b/services/room-service/internal/room/service/recovery_test.go index 4b4e60b8..63b6c4de 100644 --- a/services/room-service/internal/room/service/recovery_test.go +++ b/services/room-service/internal/room/service/recovery_test.go @@ -142,3 +142,35 @@ func TestReplayApplyRTCEventSkipsStaleMissingSeat(t *testing.T) { t.Fatalf("stale rtc events must not mutate version, got %d", current.Version) } } + +func TestReplayChangeMicSeatSkipsStaleMissingSeat(t *testing.T) { + current := state.NewRoomState("room-replay-stale-change-seat", 7601, 8, "voice") + current.OnlineUsers[7601] = &state.RoomUserState{UserID: 7601, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000} + + if err := replay(current, &command.ChangeMicSeat{ + Base: command.Base{ActorID: 7601, Room: current.RoomID, SentAtMS: 2000}, + TargetUserID: 7601, + SeatNo: 2, + }, 2000); err != nil { + t.Fatalf("stale change seat without source seat must be skipped: %v", err) + } + if current.Version != 0 { + t.Fatalf("stale change seat must not mutate version, got %d", current.Version) + } +} + +func TestReplaySetMicMuteSkipsStaleMissingSeat(t *testing.T) { + current := state.NewRoomState("room-replay-stale-mute", 7701, 8, "voice") + current.OnlineUsers[7701] = &state.RoomUserState{UserID: 7701, Role: "owner", JoinedAtMS: 1000, LastSeenAtMS: 1000} + + if err := replay(current, &command.SetMicMute{ + Base: command.Base{ActorID: 7701, Room: current.RoomID, SentAtMS: 2000}, + TargetUserID: 7701, + Muted: true, + }, 2000); err != nil { + t.Fatalf("stale mute without seat must be skipped: %v", err) + } + if current.Version != 0 { + t.Fatalf("stale mute must not mutate version, got %d", current.Version) + } +}