146 lines
4.6 KiB
Go
146 lines
4.6 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"
|
|
)
|
|
|
|
const defaultSystemEvictReason = "platform_moderation"
|
|
|
|
// SystemEvictUser removes a platform-banned or risk-blocked user from their current Room Cell.
|
|
// It deliberately bypasses room manager role checks, but still serializes through the Room Cell command log.
|
|
func (s *Service) SystemEvictUser(ctx context.Context, req *roomv1.SystemEvictUserRequest) (*roomv1.SystemEvictUserResponse, error) {
|
|
ctx = contextFromMeta(ctx, req.GetMeta())
|
|
targetUserID := req.GetTargetUserId()
|
|
if targetUserID <= 0 {
|
|
return nil, xerr.New(xerr.InvalidArgument, "target_user_id is required")
|
|
}
|
|
|
|
roomID := strings.TrimSpace(req.GetMeta().GetRoomId())
|
|
if roomID == "" {
|
|
presence, exists, err := s.repository.GetCurrentRoomPresence(ctx, targetUserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !exists {
|
|
return &roomv1.SystemEvictUserResponse{
|
|
HadCurrentRoom: false,
|
|
Result: commandResult(false, 0, s.clock.Now()),
|
|
}, nil
|
|
}
|
|
roomID = presence.RoomID
|
|
}
|
|
|
|
base := baseFromMeta(req.GetMeta())
|
|
base.Room = roomID
|
|
if base.ActorID <= 0 {
|
|
// mutateRoom requires a non-zero actor for idempotency metadata; system eviction does not use it for authorization.
|
|
base.ActorID = targetUserID
|
|
}
|
|
cmd := command.SystemEvictUser{
|
|
Base: base,
|
|
TargetUserID: targetUserID,
|
|
OperatorUserID: req.GetOperatorUserId(),
|
|
Reason: normalizeSystemEvictReason(req.GetReason()),
|
|
BanFromRoom: req.GetBanFromRoom(),
|
|
}
|
|
|
|
shouldKickRTC := false
|
|
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
|
|
}
|
|
|
|
_, inRoom := current.OnlineUsers[cmd.TargetUserID]
|
|
_, onSeat := current.SeatByUser(cmd.TargetUserID)
|
|
alreadyBanned := current.BanUsers[cmd.TargetUserID]
|
|
if !inRoom && !onSeat && !current.AdminUsers[cmd.TargetUserID] && (!cmd.BanFromRoom || alreadyBanned) {
|
|
// 重复系统驱逐保持幂等;没有残留状态时只返回当前快照。
|
|
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
|
}
|
|
shouldKickRTC = inRoom || onSeat
|
|
|
|
var releasedSeatNo int32
|
|
var releasedMicSessionID string
|
|
if seat, exists := current.SeatByUser(cmd.TargetUserID); exists {
|
|
index := current.SeatIndex(seat.SeatNo)
|
|
releasedSeatNo = seat.SeatNo
|
|
releasedMicSessionID = current.MicSeats[index].MicSessionID
|
|
current.ClearMicSession(index)
|
|
}
|
|
|
|
delete(current.OnlineUsers, cmd.TargetUserID)
|
|
delete(current.AdminUsers, cmd.TargetUserID)
|
|
if cmd.BanFromRoom {
|
|
current.BanUsers[cmd.TargetUserID] = true
|
|
}
|
|
current.Version++
|
|
|
|
evictEvent, err := outbox.Build(current.RoomID, "RoomUserKicked", current.Version, now, &roomeventsv1.RoomUserKicked{
|
|
ActorUserId: 0,
|
|
TargetUserId: cmd.TargetUserID,
|
|
})
|
|
if err != nil {
|
|
return mutationResult{}, nil, err
|
|
}
|
|
records := make([]outbox.Record, 0, 2)
|
|
if releasedMicSessionID != "" {
|
|
micEvent, err := buildMicDownEventForSeat(current.RoomID, current.Version, now, 0, cmd.TargetUserID, releasedSeatNo, releasedMicSessionID, cmd.Reason)
|
|
if err != nil {
|
|
return mutationResult{}, nil, err
|
|
}
|
|
records = append(records, micEvent)
|
|
}
|
|
records = append(records, evictEvent)
|
|
|
|
return mutationResult{
|
|
snapshot: current.ToProto(),
|
|
syncEvent: &tencentim.RoomEvent{
|
|
EventID: evictEvent.EventID,
|
|
RoomID: current.RoomID,
|
|
EventType: "room_user_kicked",
|
|
TargetUserID: cmd.TargetUserID,
|
|
RoomVersion: current.Version,
|
|
Attributes: map[string]string{"reason": cmd.Reason},
|
|
},
|
|
}, records, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response := &roomv1.SystemEvictUserResponse{
|
|
HadCurrentRoom: true,
|
|
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
|
Room: result.snapshot,
|
|
RoomId: roomID,
|
|
}
|
|
if result.applied && shouldKickRTC && s.rtcUserRemover != nil {
|
|
if err := s.rtcUserRemover.RemoveUserByStrRoomID(ctx, roomID, targetUserID); err != nil {
|
|
response.RtcKickError = err.Error()
|
|
} else {
|
|
response.RtcKicked = true
|
|
}
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func normalizeSystemEvictReason(reason string) string {
|
|
reason = strings.TrimSpace(reason)
|
|
if reason == "" {
|
|
return defaultSystemEvictReason
|
|
}
|
|
return reason
|
|
}
|