361 lines
12 KiB
Go
361 lines
12 KiB
Go
package roomadmin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"hyapp-admin-server/internal/integration/robotclient"
|
|
"hyapp-admin-server/internal/integration/roomclient"
|
|
"hyapp-admin-server/internal/modules/shared"
|
|
)
|
|
|
|
type RobotRoom struct {
|
|
AppCode string `json:"appCode"`
|
|
RoomID string `json:"roomId"`
|
|
RoomShortID string `json:"roomShortId"`
|
|
Title string `json:"title"`
|
|
CoverURL string `json:"coverUrl"`
|
|
VisibleRegionID int64 `json:"visibleRegionId"`
|
|
RegionName string `json:"regionName"`
|
|
Status string `json:"status"`
|
|
OwnerRobotUserID string `json:"ownerRobotUserId"`
|
|
Owner RoomOwner `json:"owner"`
|
|
RobotUserIDs []string `json:"robotUserIds"`
|
|
Robots []RoomOwner `json:"robots"`
|
|
GiftRule RobotGiftRule `json:"giftRule"`
|
|
CreatedByAdminID uint64 `json:"createdByAdminId"`
|
|
CreatedAtMS int64 `json:"createdAtMs"`
|
|
UpdatedAtMS int64 `json:"updatedAtMs"`
|
|
}
|
|
|
|
type RobotGiftRule struct {
|
|
GiftIDs []string `json:"giftIds"`
|
|
LuckyGiftIDs []string `json:"luckyGiftIds"`
|
|
NormalGiftIntervalMS int64 `json:"normalGiftIntervalMs"`
|
|
LuckyComboMin int64 `json:"luckyComboMin"`
|
|
LuckyComboMax int64 `json:"luckyComboMax"`
|
|
LuckyPauseMinMS int64 `json:"luckyPauseMinMs"`
|
|
LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"`
|
|
}
|
|
|
|
type AvailableRoomRobot struct {
|
|
UserID string `json:"userId"`
|
|
Status string `json:"status"`
|
|
RoomScene string `json:"roomScene"`
|
|
LastUsedAtMS int64 `json:"lastUsedAtMs"`
|
|
UsedCount int64 `json:"usedCount"`
|
|
User RoomOwner `json:"user"`
|
|
}
|
|
|
|
func (s *Service) ListRobotRooms(ctx context.Context, query robotRoomListQuery) ([]RobotRoom, int64, error) {
|
|
if s.roomClient == nil {
|
|
return nil, 0, fmt.Errorf("room service client is not configured")
|
|
}
|
|
if query.Page <= 0 {
|
|
query.Page = 1
|
|
}
|
|
if query.PageSize <= 0 || query.PageSize > 100 {
|
|
query.PageSize = 50
|
|
}
|
|
result, err := s.roomClient.ListRobotRooms(ctx, roomclient.ListRobotRoomsRequest{
|
|
Page: query.Page,
|
|
PageSize: query.PageSize,
|
|
Status: normalizeRobotRoomListStatus(query.Status),
|
|
})
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
items := robotRoomsFromClient(result.Rooms)
|
|
if err := s.fillRobotRoomDetails(ctx, items); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
return items, result.Total, nil
|
|
}
|
|
|
|
func normalizeRobotRoomListStatus(status string) string {
|
|
switch strings.TrimSpace(status) {
|
|
case "active", "stopped":
|
|
return strings.TrimSpace(status)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (s *Service) ListAvailableRoomRobots(ctx context.Context) ([]AvailableRoomRobot, error) {
|
|
if s.robotClient == nil || s.roomClient == nil {
|
|
return nil, fmt.Errorf("robot or room service client is not configured")
|
|
}
|
|
result, err := s.robotClient.ListGameRobots(ctx, robotclient.ListGameRobotsRequest{
|
|
Status: "active",
|
|
PageSize: 200,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
userIDs := make([]int64, 0, len(result.Robots))
|
|
for _, item := range result.Robots {
|
|
if item.UserID > 0 {
|
|
userIDs = append(userIDs, item.UserID)
|
|
}
|
|
}
|
|
// 全站机器人是后台统一维护的机器人账号池;房间机器人下拉只把这批账号交给 room-service 做占用过滤,
|
|
// 避免新增了全站机器人却因为旧的 room-scene 专用池为空而无法选择房主机器人。
|
|
available, err := s.roomClient.FilterAvailableRoomRobots(ctx, userIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
owners, err := s.queryRoomOwners(ctx, available.AvailableUserIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return availableRoomRobotsFromGamePool(result.Robots, available.AvailableUserIDs, owners), nil
|
|
}
|
|
|
|
func availableRoomRobotsFromGamePool(robots []robotclient.Robot, availableUserIDs []int64, owners map[int64]RoomOwner) []AvailableRoomRobot {
|
|
allowed := make(map[int64]bool, len(availableUserIDs))
|
|
for _, userID := range availableUserIDs {
|
|
allowed[userID] = true
|
|
}
|
|
items := make([]AvailableRoomRobot, 0, len(availableUserIDs))
|
|
for _, robot := range robots {
|
|
if !allowed[robot.UserID] {
|
|
continue
|
|
}
|
|
items = append(items, AvailableRoomRobot{
|
|
UserID: strconv.FormatInt(robot.UserID, 10),
|
|
Status: robot.Status,
|
|
RoomScene: robot.RoomScene,
|
|
LastUsedAtMS: robot.LastUsedAtMS,
|
|
UsedCount: robot.UsedCount,
|
|
User: owners[robot.UserID],
|
|
})
|
|
}
|
|
return items
|
|
}
|
|
|
|
func (s *Service) CreateRobotRoom(ctx context.Context, req createRobotRoomRequest, actor shared.Actor) (RobotRoom, error) {
|
|
if s.roomClient == nil {
|
|
return RobotRoom{}, fmt.Errorf("room service client is not configured")
|
|
}
|
|
normalized, err := normalizeCreateRobotRoomRequest(req)
|
|
if err != nil {
|
|
return RobotRoom{}, err
|
|
}
|
|
req = normalized
|
|
if err := s.applyOwnerProfileToRobotRoomRequest(ctx, &req); err != nil {
|
|
return RobotRoom{}, err
|
|
}
|
|
created, err := s.roomClient.CreateRobotRoom(ctx, roomclient.CreateRobotRoomRequest{
|
|
OwnerRobotUserID: int64(req.OwnerRobotUserID),
|
|
CandidateRobotUserIDs: []int64(req.CandidateRobotUserIDs),
|
|
MinRobotCount: req.MinRobotCount,
|
|
MaxRobotCount: req.MaxRobotCount,
|
|
RoomName: req.RoomName,
|
|
RoomAvatar: req.RoomAvatar,
|
|
VisibleRegionID: req.VisibleRegionID,
|
|
GiftRule: roomclient.RobotRoomGiftRule{
|
|
GiftIDs: req.GiftIDs,
|
|
LuckyGiftIDs: req.LuckyGiftIDs,
|
|
NormalGiftIntervalMS: req.NormalGiftIntervalMS,
|
|
LuckyComboMin: req.LuckyComboMin,
|
|
LuckyComboMax: req.LuckyComboMax,
|
|
LuckyPauseMinMS: req.LuckyPauseMinMS,
|
|
LuckyPauseMaxMS: req.LuckyPauseMaxMS,
|
|
},
|
|
AdminID: uint64(actor.UserID),
|
|
})
|
|
if err != nil {
|
|
return RobotRoom{}, mapRoomClientError(err)
|
|
}
|
|
items := []RobotRoom{robotRoomFromClient(created)}
|
|
if err := s.fillRobotRoomDetails(ctx, items); err != nil {
|
|
return RobotRoom{}, err
|
|
}
|
|
return items[0], nil
|
|
}
|
|
|
|
func (s *Service) applyOwnerProfileToRobotRoomRequest(ctx context.Context, req *createRobotRoomRequest) error {
|
|
if s.userDB == nil {
|
|
return fmt.Errorf("user database is not configured")
|
|
}
|
|
ownerID := int64(req.OwnerRobotUserID)
|
|
owners, err := s.queryRoomOwners(ctx, []int64{ownerID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
owner, ok := owners[ownerID]
|
|
if !ok {
|
|
return fmt.Errorf("%w: 房主机器人资料不存在", ErrInvalidArgument)
|
|
}
|
|
roomName, roomAvatar, err := robotRoomProfileFromOwner(owner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// 机器人房间对外展示必须跟随房主机器人资料,避免后台表单或脚本传入的自定义值让房间卡片和房主身份不一致。
|
|
req.RoomName = roomName
|
|
req.RoomAvatar = roomAvatar
|
|
return nil
|
|
}
|
|
|
|
func robotRoomProfileFromOwner(owner RoomOwner) (string, string, error) {
|
|
name := strings.TrimSpace(owner.Username)
|
|
avatar := strings.TrimSpace(owner.Avatar)
|
|
if name == "" {
|
|
return "", "", fmt.Errorf("%w: 房主机器人名称不能为空", ErrInvalidArgument)
|
|
}
|
|
if avatar == "" {
|
|
return "", "", fmt.Errorf("%w: 房主机器人头像不能为空", ErrInvalidArgument)
|
|
}
|
|
return name, avatar, nil
|
|
}
|
|
|
|
func (s *Service) SetRobotRoomStatus(ctx context.Context, roomID string, status string, actor shared.Actor) (RobotRoom, error) {
|
|
if s.roomClient == nil {
|
|
return RobotRoom{}, fmt.Errorf("room service client is not configured")
|
|
}
|
|
roomID = strings.TrimSpace(roomID)
|
|
status = strings.TrimSpace(status)
|
|
if roomID == "" || (status != "active" && status != "stopped") {
|
|
return RobotRoom{}, fmt.Errorf("%w: 机器人房间状态参数不正确", ErrInvalidArgument)
|
|
}
|
|
item, err := s.roomClient.SetRobotRoomStatus(ctx, roomclient.SetRobotRoomStatusRequest{
|
|
RoomID: roomID,
|
|
Status: status,
|
|
AdminID: uint64(actor.UserID),
|
|
})
|
|
if err != nil {
|
|
return RobotRoom{}, mapRoomClientError(err)
|
|
}
|
|
items := []RobotRoom{robotRoomFromClient(item)}
|
|
if err := s.fillRobotRoomDetails(ctx, items); err != nil {
|
|
return RobotRoom{}, err
|
|
}
|
|
return items[0], nil
|
|
}
|
|
|
|
func normalizeCreateRobotRoomRequest(req createRobotRoomRequest) (createRobotRoomRequest, error) {
|
|
ownerID := int64(req.OwnerRobotUserID)
|
|
if ownerID <= 0 {
|
|
return req, fmt.Errorf("%w: 请选择房主机器人", ErrInvalidArgument)
|
|
}
|
|
if req.MinRobotCount <= 0 || req.MaxRobotCount < req.MinRobotCount {
|
|
return req, fmt.Errorf("%w: 机器人数量范围不正确", ErrInvalidArgument)
|
|
}
|
|
candidateIDs := normalizeRobotRoomCandidateIDs([]int64(req.CandidateRobotUserIDs), ownerID)
|
|
if len(candidateIDs) == 0 {
|
|
return req, fmt.Errorf("%w: 请选择候选机器人", ErrInvalidArgument)
|
|
}
|
|
if len(req.GiftIDs) == 0 {
|
|
return req, fmt.Errorf("%w: 请选择普通礼物合集", ErrInvalidArgument)
|
|
}
|
|
if len(req.LuckyGiftIDs) == 0 {
|
|
return req, fmt.Errorf("%w: 请选择幸运礼物合集", ErrInvalidArgument)
|
|
}
|
|
if req.NormalGiftIntervalMS <= 0 {
|
|
return req, fmt.Errorf("%w: 普通礼物频次不正确", ErrInvalidArgument)
|
|
}
|
|
if req.LuckyComboMin <= 0 || req.LuckyComboMax < req.LuckyComboMin {
|
|
return req, fmt.Errorf("%w: 幸运礼物连击范围不正确", ErrInvalidArgument)
|
|
}
|
|
if req.LuckyPauseMinMS <= 0 || req.LuckyPauseMaxMS < req.LuckyPauseMinMS {
|
|
return req, fmt.Errorf("%w: 幸运礼物间隔范围不正确", ErrInvalidArgument)
|
|
}
|
|
req.CandidateRobotUserIDs = flexibleJSONInt64Slice(candidateIDs)
|
|
return req, nil
|
|
}
|
|
|
|
func normalizeRobotRoomCandidateIDs(values []int64, ownerID int64) []int64 {
|
|
seen := make(map[int64]bool, len(values))
|
|
out := make([]int64, 0, len(values))
|
|
for _, value := range values {
|
|
// 房主由 room-service 单独并入机器人集合;候选列表只保留其他机器人,避免前端重复选择或旧脚本传入重复 ID。
|
|
if value <= 0 || value == ownerID || seen[value] {
|
|
continue
|
|
}
|
|
seen[value] = true
|
|
out = append(out, value)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func robotRoomsFromClient(items []roomclient.RobotRoom) []RobotRoom {
|
|
out := make([]RobotRoom, 0, len(items))
|
|
for _, item := range items {
|
|
out = append(out, robotRoomFromClient(item))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func robotRoomFromClient(item roomclient.RobotRoom) RobotRoom {
|
|
robotUserIDs := make([]string, 0, len(item.RobotUserIDs))
|
|
for _, userID := range item.RobotUserIDs {
|
|
robotUserIDs = append(robotUserIDs, strconv.FormatInt(userID, 10))
|
|
}
|
|
return RobotRoom{
|
|
AppCode: item.AppCode,
|
|
RoomID: item.RoomID,
|
|
RoomShortID: item.RoomShortID,
|
|
Title: item.Title,
|
|
CoverURL: item.CoverURL,
|
|
VisibleRegionID: item.VisibleRegionID,
|
|
Status: item.Status,
|
|
OwnerRobotUserID: strconv.FormatInt(item.OwnerRobotUserID, 10),
|
|
RobotUserIDs: robotUserIDs,
|
|
GiftRule: RobotGiftRule{
|
|
GiftIDs: item.GiftRule.GiftIDs,
|
|
LuckyGiftIDs: item.GiftRule.LuckyGiftIDs,
|
|
NormalGiftIntervalMS: item.GiftRule.NormalGiftIntervalMS,
|
|
LuckyComboMin: item.GiftRule.LuckyComboMin,
|
|
LuckyComboMax: item.GiftRule.LuckyComboMax,
|
|
LuckyPauseMinMS: item.GiftRule.LuckyPauseMinMS,
|
|
LuckyPauseMaxMS: item.GiftRule.LuckyPauseMaxMS,
|
|
},
|
|
CreatedByAdminID: item.CreatedByAdminID,
|
|
CreatedAtMS: item.CreatedAtMS,
|
|
UpdatedAtMS: item.UpdatedAtMS,
|
|
}
|
|
}
|
|
|
|
func (s *Service) fillRobotRoomDetails(ctx context.Context, items []RobotRoom) error {
|
|
if s.userDB == nil || len(items) == 0 {
|
|
return nil
|
|
}
|
|
userIDs := make([]int64, 0)
|
|
regionIDs := make([]int64, 0)
|
|
for _, item := range items {
|
|
if ownerID, err := strconv.ParseInt(item.OwnerRobotUserID, 10, 64); err == nil && ownerID > 0 {
|
|
userIDs = append(userIDs, ownerID)
|
|
}
|
|
for _, rawID := range item.RobotUserIDs {
|
|
if userID, err := strconv.ParseInt(rawID, 10, 64); err == nil && userID > 0 {
|
|
userIDs = append(userIDs, userID)
|
|
}
|
|
}
|
|
if item.VisibleRegionID > 0 {
|
|
regionIDs = append(regionIDs, item.VisibleRegionID)
|
|
}
|
|
}
|
|
owners, err := s.queryRoomOwners(ctx, uniqueInt64s(userIDs))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
regions, err := s.queryRegionNames(ctx, uniqueInt64s(regionIDs))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for index := range items {
|
|
if ownerID, err := strconv.ParseInt(items[index].OwnerRobotUserID, 10, 64); err == nil {
|
|
items[index].Owner = owners[ownerID]
|
|
}
|
|
for _, rawID := range items[index].RobotUserIDs {
|
|
if userID, err := strconv.ParseInt(rawID, 10, 64); err == nil {
|
|
items[index].Robots = append(items[index].Robots, owners[userID])
|
|
}
|
|
}
|
|
items[index].RegionName = regions[items[index].VisibleRegionID]
|
|
}
|
|
return nil
|
|
}
|