129 lines
4.0 KiB
Go

package service
import (
"context"
"strings"
"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"
)
func (s *Service) requireRoomOpenForEntry(ctx context.Context, roomID string) error {
closed, err := s.isRoomClosed(ctx, roomID)
if err != nil || !closed {
return err
}
return xerr.New(xerr.RoomClosed, "room closed")
}
func (s *Service) isRoomClosed(ctx context.Context, roomID string) (bool, error) {
roomMeta, exists, err := s.repository.GetRoomMeta(ctx, roomID)
if err != nil || !exists {
return false, err
}
return isClosedRoomStatus(roomMeta.Status), nil
}
func isClosedRoomStatus(status string) bool {
value := strings.TrimSpace(status)
return value != "" && !strings.EqualFold(value, state.RoomStatusActive)
}
// CloseRoom 关闭房间,统一清理 presence、麦位、读模型状态和房间外事件。
func (s *Service) CloseRoom(ctx context.Context, req *roomv1.CloseRoomRequest) (*roomv1.CloseRoomResponse, error) {
ctx = contextFromMeta(ctx, req.GetMeta())
cmd := command.CloseRoom{
Base: baseFromMeta(req.GetMeta()),
Reason: strings.TrimSpace(req.GetReason()),
}
if cmd.Reason == "" {
cmd.Reason = "closed_by_owner"
}
result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
if current == nil {
return mutationResult{}, nil, xerr.New(xerr.NotFound, "room not found")
}
if current.Status == state.RoomStatusClosed {
return mutationResult{snapshot: current.ToProto()}, nil, nil
}
if current.Status != state.RoomStatusActive && current.Status != state.RoomStatusClosing {
return mutationResult{}, nil, xerr.New(xerr.Conflict, "room is not active")
}
if err := requireOwnerPresent(current, cmd.ActorUserID()); err != nil {
return mutationResult{}, nil, err
}
type occupiedMicSeat struct {
userID int64
seatNo int32
micSessionID string
}
occupiedSeats := make([]occupiedMicSeat, 0, len(current.MicSeats))
for _, seat := range current.MicSeats {
if seat.UserID != 0 && seat.MicSessionID != "" {
occupiedSeats = append(occupiedSeats, occupiedMicSeat{
userID: seat.UserID,
seatNo: seat.SeatNo,
micSessionID: seat.MicSessionID,
})
}
}
current.Status = state.RoomStatusClosed
current.OnlineUsers = map[int64]*state.RoomUserState{}
for index := range current.MicSeats {
current.ClearMicSession(index)
}
current.Version++
closedEvent, err := outbox.Build(current.RoomID, "RoomClosed", current.Version, now, &roomeventsv1.RoomClosed{
ActorUserId: cmd.ActorUserID(),
Reason: cmd.Reason,
})
if err != nil {
return mutationResult{}, nil, err
}
records := make([]outbox.Record, 0, len(occupiedSeats)+1)
for _, seat := range occupiedSeats {
// 关房是 Room Cell 统一清场,给每个占麦 session 单独写 down 事件,保证用户时长能闭合。
micEvent, err := buildMicDownEventForSeat(current.RoomID, current.Version, now, cmd.ActorUserID(), seat.userID, seat.seatNo, seat.micSessionID, "room_closed")
if err != nil {
return mutationResult{}, nil, err
}
records = append(records, micEvent)
}
records = append(records, closedEvent)
return mutationResult{
snapshot: current.ToProto(),
roomStatus: state.RoomStatusClosed,
syncEvent: &tencentim.RoomEvent{
EventID: closedEvent.EventID,
RoomID: current.RoomID,
EventType: "room_closed",
ActorUserID: cmd.ActorUserID(),
RoomVersion: current.Version,
Attributes: map[string]string{
"reason": cmd.Reason,
},
},
}, records, nil
})
if err != nil {
return nil, err
}
return &roomv1.CloseRoomResponse{
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
Room: result.snapshot,
}, nil
}