package service import ( "context" "time" roomeventsv1 "hyapp.local/api/proto/events/room/v1" roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/tencentim" "hyapp/pkg/xerr" "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" ) // RobotMicUpInput 是内部机器人编排器占麦输入;调用方必须只传 robot-service 登记过的机器人用户。 type RobotMicUpInput struct { Meta *roomv1.RequestMeta TargetUserID int64 SeatNo int32 } // RobotVirtualMicUp 把机器人放到静音虚拟麦位,不要求真实 RTC 推流。 func (s *Service) RobotVirtualMicUp(ctx context.Context, input RobotMicUpInput) (*roomv1.MicUpResponse, error) { ctx = contextFromMeta(ctx, input.Meta) cmd := command.RobotMicUp{ Base: baseFromMeta(input.Meta), TargetUserID: input.TargetUserID, SeatNo: input.SeatNo, } if cmd.TargetUserID <= 0 { cmd.TargetUserID = cmd.ActorUserID() } cmd.MicSessionID = micSessionIDForCommand(cmd.RoomID(), cmd.ID()) result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { if err := requireActiveRoom(current); err != nil { return mutationResult{}, nil, err } if _, exists := current.OnlineUsers[cmd.TargetUserID]; !exists { return mutationResult{}, nil, xerr.New(xerr.NotFound, "robot not in room") } if _, exists := current.SeatByUser(cmd.TargetUserID); exists { return mutationResult{}, nil, xerr.New(xerr.Conflict, "robot already on seat") } index := current.SeatIndex(cmd.SeatNo) if index < 0 { return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "seat does not exist") } if current.MicSeats[index].Locked { return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "seat is locked") } if current.MicSeats[index].UserID != 0 { return mutationResult{}, nil, xerr.New(xerr.Conflict, "seat is occupied") } current.Version++ current.MicSeats[index].UserID = cmd.TargetUserID current.MicSeats[index].PublishState = state.MicPublishPublishing current.MicSeats[index].MicSessionID = cmd.MicSessionID current.MicSeats[index].PublishDeadlineMS = 0 current.MicSeats[index].MicSessionRoomVersion = current.Version current.MicSeats[index].LastPublishEventTimeMS = now.UnixMilli() current.MicSeats[index].MicHeartbeatAtMS = now.UnixMilli() current.MicSeats[index].MicMuted = true 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(), TargetUserId: cmd.TargetUserID, FromSeat: 0, ToSeat: cmd.SeatNo, Action: "robot_up", MicSessionId: cmd.MicSessionID, PublishState: state.MicPublishPublishing, PublishDeadlineMs: 0, SeatStatus: seatStatus, TargetGiftValue: targetGiftValue, }) if err != nil { return mutationResult{}, nil, err } return mutationResult{ snapshot: current.ToProto(), seatNo: cmd.SeatNo, micSessionID: cmd.MicSessionID, publishDeadlineMS: 0, syncEvent: &tencentim.RoomEvent{ EventID: micEvent.EventID, RoomID: current.RoomID, EventType: "room_mic_publish_confirmed", ActorUserID: cmd.ActorUserID(), TargetUserID: cmd.TargetUserID, SeatNo: cmd.SeatNo, RoomVersion: current.Version, Attributes: map[string]string{ "action": "robot_up", "publish_state": state.MicPublishPublishing, "mic_session_id": cmd.MicSessionID, "seat_status": seatStatus, "mic_muted": "true", "target_gift_value": int64String(targetGiftValue), }, }, }, []outbox.Record{micEvent}, nil }) if err != nil { return nil, err } if result.micSessionID == "" { if seat := protoSeatByUser(result.snapshot, cmd.TargetUserID); seat != nil { result.seatNo = seat.GetSeatNo() result.micSessionID = seat.GetMicSessionId() } } return &roomv1.MicUpResponse{ Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), SeatNo: result.seatNo, Room: result.snapshot, MicSessionId: result.micSessionID, PublishDeadlineMs: 0, }, nil }