package service import ( "context" "time" roomeventsv1 "hyapp/api/proto/events/room/v1" roomv1 "hyapp/api/proto/room/v1" "hyapp/pkg/tencentim" "hyapp/services/room-service/internal/room/command" "hyapp/services/room-service/internal/room/outbox" "hyapp/services/room-service/internal/room/rank" "hyapp/services/room-service/internal/room/state" ) // MuteUser 修改用户禁言状态。 func (s *Service) MuteUser(ctx context.Context, req *roomv1.MuteUserRequest) (*roomv1.MuteUserResponse, error) { // 禁言状态由 room-service 持有,腾讯云 IM 发言前回调必须同步查询 CheckSpeakPermission。 cmd := command.MuteUser{ Base: baseFromMeta(req.GetMeta()), TargetUserID: req.GetTargetUserId(), Muted: req.GetMuted(), } result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { if cmd.Muted { // map set 表达禁言集合,true 才会被快照导出。 current.MuteUsers[cmd.TargetUserID] = true } else { // 解除禁言直接删除集合成员。 delete(current.MuteUsers, cmd.TargetUserID) } current.Version++ // 禁言变更进入 outbox,外部系统可以做审核和操作审计。 muteEvent, err := outbox.Build(current.RoomID, "RoomUserMuted", current.Version, now, &roomeventsv1.RoomUserMuted{ ActorUserId: cmd.ActorUserID(), TargetUserId: cmd.TargetUserID, Muted: cmd.Muted, }) if err != nil { return mutationResult{}, nil, err } return mutationResult{ snapshot: current.ToProto(), syncEvent: &tencentim.RoomEvent{ EventID: muteEvent.EventID, RoomID: current.RoomID, EventType: "room_user_muted", ActorUserID: cmd.ActorUserID(), TargetUserID: cmd.TargetUserID, RoomVersion: current.Version, }, }, []outbox.Record{muteEvent}, nil }) if err != nil { return nil, err } return &roomv1.MuteUserResponse{ Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), Room: result.snapshot, }, nil } // KickUser 把目标用户踢出房间并移除其麦位与 presence。 func (s *Service) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*roomv1.KickUserResponse, error) { // KickUser 同时修改 presence、麦位和 ban 集合,是典型 Room Cell 强关联命令。 cmd := command.KickUser{ Base: baseFromMeta(req.GetMeta()), TargetUserID: req.GetTargetUserId(), } result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { if seat, exists := current.SeatByUser(cmd.TargetUserID); exists { // 被踢用户如果在麦上,必须先释放麦位,避免恢复后出现幽灵占位。 index := current.SeatIndex(seat.SeatNo) current.MicSeats[index].UserID = 0 } // ban 集合会让后续 JoinRoom 和 VerifyRoomPresence 都失败。 delete(current.OnlineUsers, cmd.TargetUserID) current.BanUsers[cmd.TargetUserID] = true current.Version++ kickEvent, err := outbox.Build(current.RoomID, "RoomUserKicked", current.Version, now, &roomeventsv1.RoomUserKicked{ ActorUserId: cmd.ActorUserID(), TargetUserId: cmd.TargetUserID, }) if err != nil { return mutationResult{}, nil, err } return mutationResult{ snapshot: current.ToProto(), syncEvent: &tencentim.RoomEvent{ EventID: kickEvent.EventID, RoomID: current.RoomID, EventType: "room_user_kicked", ActorUserID: cmd.ActorUserID(), TargetUserID: cmd.TargetUserID, RoomVersion: current.Version, }, }, []outbox.Record{kickEvent}, nil }) if err != nil { return nil, err } return &roomv1.KickUserResponse{ Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), Room: result.snapshot, }, nil }