288 lines
10 KiB
Go
288 lines
10 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"time"
|
||
"unicode/utf8"
|
||
|
||
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
||
roomv1 "hyapp.local/api/proto/room/v1"
|
||
"hyapp/pkg/appcode"
|
||
"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) AdminListRooms(ctx context.Context, req *roomv1.AdminListRoomsRequest) (*roomv1.AdminListRoomsResponse, error) {
|
||
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||
rooms, total, err := s.repository.AdminListRooms(ctx, AdminRoomListQuery{
|
||
AppCode: appcode.FromContext(ctx),
|
||
Page: int(req.GetPage()),
|
||
PageSize: int(req.GetPageSize()),
|
||
Keyword: strings.TrimSpace(req.GetKeyword()),
|
||
Status: strings.TrimSpace(req.GetStatus()),
|
||
RegionID: req.GetVisibleRegionId(),
|
||
OwnerUserID: req.GetOwnerUserId(),
|
||
SortBy: strings.TrimSpace(req.GetSortBy()),
|
||
SortDirection: strings.TrimSpace(req.GetSortDirection()),
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
items := make([]*roomv1.AdminRoomListItem, 0, len(rooms))
|
||
for _, room := range rooms {
|
||
items = append(items, adminRoomListItemProto(room))
|
||
}
|
||
return &roomv1.AdminListRoomsResponse{Rooms: items, Total: total, ServerTimeMs: s.clock.Now().UnixMilli()}, nil
|
||
}
|
||
|
||
func (s *Service) AdminGetRoom(ctx context.Context, req *roomv1.AdminGetRoomRequest) (*roomv1.AdminGetRoomResponse, error) {
|
||
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||
room, exists, err := s.repository.AdminGetRoom(ctx, req.GetRoomId())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !exists {
|
||
return nil, xerr.New(xerr.NotFound, "room not found")
|
||
}
|
||
return &roomv1.AdminGetRoomResponse{Room: adminRoomListItemProto(room), ServerTimeMs: s.clock.Now().UnixMilli()}, nil
|
||
}
|
||
|
||
func (s *Service) AdminUpdateRoom(ctx context.Context, req *roomv1.AdminUpdateRoomRequest) (*roomv1.AdminUpdateRoomResponse, error) {
|
||
ctx = contextFromMeta(ctx, req.GetMeta())
|
||
cmd, err := s.adminUpdateCommand(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
result, err := s.applyAdminRoomMutation(ctx, cmd, cmd)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &roomv1.AdminUpdateRoomResponse{
|
||
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
||
Room: result.snapshot,
|
||
}, nil
|
||
}
|
||
|
||
func (s *Service) AdminDeleteRoom(ctx context.Context, req *roomv1.AdminDeleteRoomRequest) (*roomv1.AdminDeleteRoomResponse, error) {
|
||
ctx = contextFromMeta(ctx, req.GetMeta())
|
||
if req.GetMeta() == nil || strings.TrimSpace(req.GetMeta().GetRoomId()) == "" {
|
||
return nil, xerr.New(xerr.InvalidArgument, "room_id is required")
|
||
}
|
||
cmd := command.AdminDeleteRoom{
|
||
Base: baseFromMeta(req.GetMeta()),
|
||
AdminID: req.GetAdminId(),
|
||
AdminName: strings.TrimSpace(req.GetAdminName()),
|
||
}
|
||
patch := command.AdminUpdateRoom{
|
||
Base: cmd.Base,
|
||
Status: stringPtr(state.RoomStatusDeleted),
|
||
CloseReason: "admin_deleted",
|
||
AdminID: cmd.AdminID,
|
||
AdminName: cmd.AdminName,
|
||
}
|
||
result, err := s.applyAdminRoomMutation(ctx, cmd, patch)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &roomv1.AdminDeleteRoomResponse{
|
||
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
||
Room: result.snapshot,
|
||
}, nil
|
||
}
|
||
|
||
func (s *Service) adminUpdateCommand(req *roomv1.AdminUpdateRoomRequest) (command.AdminUpdateRoom, error) {
|
||
if req == nil || req.GetMeta() == nil || strings.TrimSpace(req.GetMeta().GetRoomId()) == "" {
|
||
return command.AdminUpdateRoom{}, xerr.New(xerr.InvalidArgument, "room_id is required")
|
||
}
|
||
cmd := command.AdminUpdateRoom{
|
||
Base: baseFromMeta(req.GetMeta()),
|
||
CloseReason: strings.TrimSpace(req.GetCloseReason()),
|
||
AdminID: req.GetAdminId(),
|
||
AdminName: strings.TrimSpace(req.GetAdminName()),
|
||
}
|
||
if req.Title != nil {
|
||
value := strings.TrimSpace(req.GetTitle())
|
||
if value == "" {
|
||
return command.AdminUpdateRoom{}, xerr.New(xerr.InvalidArgument, "room title is required")
|
||
}
|
||
if utf8.RuneCountInString(value) > maxRoomNameRunes {
|
||
return command.AdminUpdateRoom{}, xerr.New(xerr.InvalidArgument, "room title is too long")
|
||
}
|
||
cmd.Title = &value
|
||
}
|
||
if req.CoverUrl != nil {
|
||
value := strings.TrimSpace(req.GetCoverUrl())
|
||
if utf8.RuneCountInString(value) > maxRoomAvatarRunes {
|
||
return command.AdminUpdateRoom{}, xerr.New(xerr.InvalidArgument, "room cover is too long")
|
||
}
|
||
if value == "" {
|
||
value = defaultRoomAvatar
|
||
}
|
||
cmd.CoverURL = &value
|
||
}
|
||
if req.Description != nil {
|
||
value := strings.TrimSpace(req.GetDescription())
|
||
if utf8.RuneCountInString(value) > maxRoomDescriptionRunes {
|
||
return command.AdminUpdateRoom{}, xerr.New(xerr.InvalidArgument, "room description is too long")
|
||
}
|
||
cmd.Description = &value
|
||
}
|
||
if req.Mode != nil {
|
||
value := strings.TrimSpace(req.GetMode())
|
||
if value == "" || utf8.RuneCountInString(value) > 64 {
|
||
return command.AdminUpdateRoom{}, xerr.New(xerr.InvalidArgument, "room mode is invalid")
|
||
}
|
||
cmd.Mode = &value
|
||
}
|
||
if req.Status != nil {
|
||
value := strings.TrimSpace(req.GetStatus())
|
||
switch value {
|
||
case state.RoomStatusActive, state.RoomStatusClosed, state.RoomStatusDeleted:
|
||
cmd.Status = &value
|
||
default:
|
||
return command.AdminUpdateRoom{}, xerr.New(xerr.InvalidArgument, "room status is invalid")
|
||
}
|
||
if value != state.RoomStatusActive && strings.TrimSpace(cmd.CloseReason) == "" {
|
||
return command.AdminUpdateRoom{}, xerr.New(xerr.InvalidArgument, "close reason is required")
|
||
}
|
||
}
|
||
if req.VisibleRegionId != nil {
|
||
value := req.GetVisibleRegionId()
|
||
if value < 0 {
|
||
return command.AdminUpdateRoom{}, xerr.New(xerr.InvalidArgument, "visible_region_id is invalid")
|
||
}
|
||
cmd.VisibleRegionID = &value
|
||
}
|
||
return cmd, nil
|
||
}
|
||
|
||
func (s *Service) applyAdminRoomMutation(ctx context.Context, logCommand command.Command, cmd command.AdminUpdateRoom) (mutationResult, error) {
|
||
return s.mutateRoom(ctx, logCommand, 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")
|
||
}
|
||
changed := applyAdminRoomPatch(current, cmd)
|
||
records := make([]outbox.Record, 0, 2)
|
||
result := mutationResult{}
|
||
if cmd.Status != nil {
|
||
switch *cmd.Status {
|
||
case state.RoomStatusClosed, state.RoomStatusDeleted:
|
||
kickTargets := closeRoomKickTargets(current)
|
||
current.OnlineUsers = map[int64]*state.RoomUserState{}
|
||
for index := range current.MicSeats {
|
||
current.ClearMicSession(index)
|
||
}
|
||
closedEvent, err := outbox.Build(current.RoomID, "RoomClosed", current.Version+1, now, &roomeventsv1.RoomClosed{
|
||
ActorUserId: cmd.ActorUserID(),
|
||
Reason: cmd.CloseReason,
|
||
})
|
||
if err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
records = append(records, closedEvent)
|
||
for _, targetUserID := range kickTargets {
|
||
kickEvent, err := outbox.Build(current.RoomID, "RoomUserKicked", current.Version+1, now, &roomeventsv1.RoomUserKicked{
|
||
ActorUserId: cmd.ActorUserID(),
|
||
TargetUserId: targetUserID,
|
||
})
|
||
if err != nil {
|
||
return mutationResult{}, nil, err
|
||
}
|
||
records = append(records, kickEvent)
|
||
}
|
||
result.roomStatus = *cmd.Status
|
||
result.closeInfo = &RoomCloseInfo{Reason: cmd.CloseReason, AdminID: cmd.AdminID, AdminName: cmd.AdminName, ClosedAtMS: now.UnixMilli()}
|
||
result.syncEvent = &tencentim.RoomEvent{EventID: closedEvent.EventID, RoomID: current.RoomID, EventType: "room_closed", ActorUserID: cmd.ActorUserID(), RoomVersion: current.Version + 1}
|
||
case state.RoomStatusActive:
|
||
result.roomStatus = state.RoomStatusActive
|
||
result.closeInfo = &RoomCloseInfo{ClearOnOpen: true}
|
||
}
|
||
}
|
||
if cmd.Mode != nil {
|
||
result.roomMode = cmd.Mode
|
||
}
|
||
if cmd.VisibleRegionID != nil {
|
||
result.visibleRegionID = cmd.VisibleRegionID
|
||
}
|
||
if !changed && len(records) == 0 && result.roomMode == nil && result.visibleRegionID == nil && result.roomStatus == "" {
|
||
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
||
}
|
||
current.Version++
|
||
result.snapshot = current.ToProto()
|
||
return result, records, nil
|
||
})
|
||
}
|
||
|
||
func applyAdminRoomPatch(current *state.RoomState, cmd command.AdminUpdateRoom) bool {
|
||
changed := false
|
||
if current.RoomExt == nil {
|
||
current.RoomExt = map[string]string{}
|
||
}
|
||
if cmd.Title != nil && current.RoomExt[roomExtTitleKey] != *cmd.Title {
|
||
current.RoomExt[roomExtTitleKey] = *cmd.Title
|
||
changed = true
|
||
}
|
||
if cmd.CoverURL != nil && current.RoomExt[roomExtCoverURLKey] != *cmd.CoverURL {
|
||
current.RoomExt[roomExtCoverURLKey] = *cmd.CoverURL
|
||
changed = true
|
||
}
|
||
if cmd.Description != nil && current.RoomExt[roomExtDescriptionKey] != *cmd.Description {
|
||
current.RoomExt[roomExtDescriptionKey] = *cmd.Description
|
||
changed = true
|
||
}
|
||
if cmd.Mode != nil && current.Mode != *cmd.Mode {
|
||
current.Mode = *cmd.Mode
|
||
changed = true
|
||
}
|
||
if cmd.VisibleRegionID != nil && current.VisibleRegionID != *cmd.VisibleRegionID {
|
||
current.VisibleRegionID = *cmd.VisibleRegionID
|
||
changed = true
|
||
}
|
||
if cmd.OwnerCountryCode != nil {
|
||
// 房主国家只作为发现页分类投影字段保存在 RoomExt;后台 HTTP 不开放直改入口,只有 user-service 国家事实同步会写入。
|
||
ownerCountryCode := normalizeRoomCountryCode(*cmd.OwnerCountryCode)
|
||
if current.RoomExt[roomExtOwnerCountryKey] != ownerCountryCode {
|
||
current.RoomExt[roomExtOwnerCountryKey] = ownerCountryCode
|
||
changed = true
|
||
}
|
||
}
|
||
if cmd.Status != nil && current.Status != *cmd.Status {
|
||
current.Status = *cmd.Status
|
||
changed = true
|
||
}
|
||
return changed
|
||
}
|
||
|
||
func adminRoomListItemProto(item AdminRoomListEntry) *roomv1.AdminRoomListItem {
|
||
return &roomv1.AdminRoomListItem{
|
||
RoomId: item.RoomID,
|
||
RoomShortId: item.RoomShortID,
|
||
VisibleRegionId: item.VisibleRegionID,
|
||
OwnerUserId: item.OwnerUserID,
|
||
Title: item.Title,
|
||
CoverUrl: item.CoverURL,
|
||
Mode: item.Mode,
|
||
Status: item.Status,
|
||
CloseReason: item.CloseReason,
|
||
ClosedByAdminId: item.ClosedByAdminID,
|
||
ClosedByAdminName: item.ClosedByAdminName,
|
||
ClosedAtMs: item.ClosedAtMS,
|
||
Heat: item.Heat,
|
||
OnlineCount: item.OnlineCount,
|
||
SeatCount: item.SeatCount,
|
||
OccupiedSeatCount: item.OccupiedSeatCount,
|
||
SortScore: item.SortScore,
|
||
CreatedAtMs: item.CreatedAtMS,
|
||
UpdatedAtMs: item.UpdatedAtMS,
|
||
}
|
||
}
|
||
|
||
func stringPtr(value string) *string {
|
||
return &value
|
||
}
|