675 lines
21 KiB
Go

package roomclient
import (
"context"
"fmt"
"strings"
"time"
"hyapp-admin-server/internal/appctx"
roomv1 "hyapp.local/api/proto/room/v1"
"google.golang.org/grpc"
)
type Client interface {
ListRooms(ctx context.Context, req ListRoomsRequest) (*ListRoomsResult, error)
GetRoom(ctx context.Context, req GetRoomRequest) (*Room, error)
UpdateRoom(ctx context.Context, req UpdateRoomRequest) (*CloseRoomResult, error)
DeleteRoom(ctx context.Context, req DeleteRoomRequest) (*CloseRoomResult, error)
GetRoomRocketConfig(ctx context.Context) (RoomRocketConfig, error)
UpdateRoomRocketConfig(ctx context.Context, req UpdateRoomRocketConfigRequest) (RoomRocketConfig, error)
GetRoomSeatConfig(ctx context.Context) (RoomSeatConfig, error)
UpdateRoomSeatConfig(ctx context.Context, req UpdateRoomSeatConfigRequest) (RoomSeatConfig, error)
ListRoomPins(ctx context.Context, req ListRoomPinsRequest) (ListRoomPinsResult, error)
CreateRoomPin(ctx context.Context, req CreateRoomPinRequest) (RoomPin, error)
CancelRoomPin(ctx context.Context, req CancelRoomPinRequest) (RoomPin, error)
CloseRoom(ctx context.Context, req CloseRoomRequest) (*CloseRoomResult, error)
ReopenRoom(ctx context.Context, req ReopenRoomRequest) (*CloseRoomResult, error)
}
type ListRoomsRequest struct {
Page int
PageSize int
Keyword string
Status string
RegionID int64
SortBy string
SortDirection string
}
type ListRoomsResult struct {
Rooms []Room
Total int64
ServerTimeMs int64
}
type GetRoomRequest struct {
RoomID string
}
type UpdateRoomRequest struct {
RequestID string
RoomID string
ActorUserID int64
Title *string
CoverURL *string
Description *string
Mode *string
Status *string
VisibleRegionID *int64
CloseReason string
AdminID uint64
AdminName string
}
type DeleteRoomRequest struct {
RequestID string
RoomID string
ActorUserID int64
AdminID uint64
AdminName string
}
type Room struct {
RoomID string
RoomShortID string
VisibleRegionID int64
OwnerUserID int64
Title string
CoverURL string
Mode string
Status string
CloseReason string
ClosedByAdminID uint64
ClosedByAdminName string
ClosedAtMS int64
Heat int64
OnlineCount int32
SeatCount int32
OccupiedSeatCount int32
SortScore int64
CreatedAtMS int64
UpdatedAtMS int64
}
type RoomRocketConfig struct {
AppCode string
Enabled bool
ConfigVersion int64
FuelSource string
LaunchDelayMS int64
BroadcastEnabled bool
BroadcastScope string
BroadcastDelayMS int64
RewardStackPolicy string
Levels []RoomRocketLevelConfig
GiftFuelRules []GiftFuelRuleConfig
UpdatedByAdminID int64
CreatedAtMS int64
UpdatedAtMS int64
}
type RoomRocketLevelConfig struct {
Level int32
FuelThreshold int64
CoverURL string
AnimationURL string
LaunchAnimationURL string
LaunchedImageURL string
InRoomRewards []RoomRocketRewardItem
Top1Rewards []RoomRocketRewardItem
IgniterRewards []RoomRocketRewardItem
}
type RoomRocketRewardItem struct {
RewardItemID string
ResourceGroupID int64
Weight int64
DisplayName string
IconURL string
}
type GiftFuelRuleConfig struct {
RuleID string
GiftID string
GiftTypeCode string
MultiplierPPM int64
FixedFuel int64
Excluded bool
}
type UpdateRoomRocketConfigRequest struct {
Config RoomRocketConfig
AdminID int64
}
type RoomSeatConfig struct {
CandidateSeatCounts []int32
AllowedSeatCounts []int32
DefaultSeatCount int32
UpdatedAtMS int64
}
type UpdateRoomSeatConfigRequest struct {
AllowedSeatCounts []int32
DefaultSeatCount int32
}
type RoomPinRoom struct {
RoomID string
RoomShortID string
VisibleRegionID int64
OwnerUserID int64
Title string
CoverURL string
Status string
}
type RoomPin struct {
ID int64
VisibleRegionID int64
RoomID string
Weight int64
Status string
PinnedAtMS int64
ExpiresAtMS int64
CancelledAtMS int64
CreatedByAdminID uint64
CancelledByAdminID uint64
CreatedAtMS int64
UpdatedAtMS int64
Room RoomPinRoom
}
type ListRoomPinsRequest struct {
Page int
PageSize int
Keyword string
Status string
RegionID int64
}
type ListRoomPinsResult struct {
Pins []RoomPin
Total int64
}
type CreateRoomPinRequest struct {
RoomID string
Weight int64
DurationDays int64
AdminID uint64
}
type CancelRoomPinRequest struct {
PinID int64
AdminID uint64
}
type CloseRoomRequest struct {
RequestID string
RoomID string
ActorUserID int64
Reason string
}
type CloseRoomResult struct {
Applied bool
RoomVersion int64
ServerTimeMs int64
RoomStatus string
OnlineUserNum int
}
type ReopenRoomRequest struct {
RequestID string
RoomID string
ActorUserID int64
}
type GRPCClient struct {
client roomv1.RoomCommandServiceClient
queryClient roomv1.RoomQueryServiceClient
}
func NewGRPC(conn grpc.ClientConnInterface) *GRPCClient {
return &GRPCClient{client: roomv1.NewRoomCommandServiceClient(conn), queryClient: roomv1.NewRoomQueryServiceClient(conn)}
}
func (c *GRPCClient) ListRooms(ctx context.Context, req ListRoomsRequest) (*ListRoomsResult, error) {
resp, err := c.queryClient.AdminListRooms(ctx, &roomv1.AdminListRoomsRequest{
Meta: requestMeta(ctx, "", 0, "admin-list-rooms"),
Page: int32(req.Page),
PageSize: int32(req.PageSize),
Keyword: strings.TrimSpace(req.Keyword),
Status: strings.TrimSpace(req.Status),
VisibleRegionId: req.RegionID,
SortBy: strings.TrimSpace(req.SortBy),
SortDirection: strings.TrimSpace(req.SortDirection),
})
if err != nil {
return nil, err
}
rooms := make([]Room, 0, len(resp.GetRooms()))
for _, item := range resp.GetRooms() {
rooms = append(rooms, roomFromProto(item))
}
return &ListRoomsResult{Rooms: rooms, Total: resp.GetTotal(), ServerTimeMs: resp.GetServerTimeMs()}, nil
}
func (c *GRPCClient) GetRoom(ctx context.Context, req GetRoomRequest) (*Room, error) {
resp, err := c.queryClient.AdminGetRoom(ctx, &roomv1.AdminGetRoomRequest{
Meta: requestMeta(ctx, strings.TrimSpace(req.RoomID), 0, "admin-get-room"),
RoomId: strings.TrimSpace(req.RoomID),
})
if err != nil {
return nil, err
}
room := roomFromProto(resp.GetRoom())
return &room, nil
}
func (c *GRPCClient) UpdateRoom(ctx context.Context, req UpdateRoomRequest) (*CloseRoomResult, error) {
resp, err := c.client.AdminUpdateRoom(ctx, &roomv1.AdminUpdateRoomRequest{
Meta: requestMeta(ctx, req.RoomID, req.ActorUserID, firstNonEmpty(req.RequestID, "admin-update-room")),
Title: req.Title,
CoverUrl: req.CoverURL,
Description: req.Description,
Mode: req.Mode,
Status: req.Status,
VisibleRegionId: req.VisibleRegionID,
CloseReason: strings.TrimSpace(req.CloseReason),
AdminId: req.AdminID,
AdminName: strings.TrimSpace(req.AdminName),
})
if err != nil {
return nil, err
}
return closeRoomResultFromCommand(resp.GetResult(), resp.GetRoom()), nil
}
func (c *GRPCClient) DeleteRoom(ctx context.Context, req DeleteRoomRequest) (*CloseRoomResult, error) {
resp, err := c.client.AdminDeleteRoom(ctx, &roomv1.AdminDeleteRoomRequest{
Meta: requestMeta(ctx, req.RoomID, req.ActorUserID, firstNonEmpty(req.RequestID, "admin-delete-room")),
AdminId: req.AdminID,
AdminName: strings.TrimSpace(req.AdminName),
})
if err != nil {
return nil, err
}
return closeRoomResultFromCommand(resp.GetResult(), resp.GetRoom()), nil
}
func (c *GRPCClient) GetRoomRocketConfig(ctx context.Context) (RoomRocketConfig, error) {
resp, err := c.queryClient.AdminGetRoomRocketConfig(ctx, &roomv1.AdminGetRoomRocketConfigRequest{
Meta: requestMeta(ctx, "", 0, "admin-get-room-rocket-config"),
})
if err != nil {
return RoomRocketConfig{}, err
}
return roomRocketConfigFromProto(resp.GetConfig()), nil
}
func (c *GRPCClient) UpdateRoomRocketConfig(ctx context.Context, req UpdateRoomRocketConfigRequest) (RoomRocketConfig, error) {
resp, err := c.client.AdminUpdateRoomRocketConfig(ctx, &roomv1.AdminUpdateRoomRocketConfigRequest{
Meta: requestMeta(ctx, "", req.AdminID, "admin-update-room-rocket-config"),
Config: roomRocketConfigToProto(req.Config),
AdminId: req.AdminID,
})
if err != nil {
return RoomRocketConfig{}, err
}
return roomRocketConfigFromProto(resp.GetConfig()), nil
}
func (c *GRPCClient) GetRoomSeatConfig(ctx context.Context) (RoomSeatConfig, error) {
resp, err := c.queryClient.AdminGetRoomSeatConfig(ctx, &roomv1.AdminGetRoomSeatConfigRequest{Meta: requestMeta(ctx, "", 0, "admin-get-room-seat-config")})
if err != nil {
return RoomSeatConfig{}, err
}
return roomSeatConfigFromProto(resp.GetConfig()), nil
}
func (c *GRPCClient) UpdateRoomSeatConfig(ctx context.Context, req UpdateRoomSeatConfigRequest) (RoomSeatConfig, error) {
resp, err := c.client.AdminUpdateRoomSeatConfig(ctx, &roomv1.AdminUpdateRoomSeatConfigRequest{
Meta: requestMeta(ctx, "", 0, "admin-update-room-seat-config"),
AllowedSeatCounts: req.AllowedSeatCounts,
DefaultSeatCount: req.DefaultSeatCount,
})
if err != nil {
return RoomSeatConfig{}, err
}
return roomSeatConfigFromProto(resp.GetConfig()), nil
}
func (c *GRPCClient) ListRoomPins(ctx context.Context, req ListRoomPinsRequest) (ListRoomPinsResult, error) {
resp, err := c.queryClient.AdminListRoomPins(ctx, &roomv1.AdminListRoomPinsRequest{
Meta: requestMeta(ctx, "", 0, "admin-list-room-pins"),
Page: int32(req.Page),
PageSize: int32(req.PageSize),
Keyword: strings.TrimSpace(req.Keyword),
Status: strings.TrimSpace(req.Status),
VisibleRegionId: req.RegionID,
})
if err != nil {
return ListRoomPinsResult{}, err
}
pins := make([]RoomPin, 0, len(resp.GetPins()))
for _, pin := range resp.GetPins() {
pins = append(pins, roomPinFromProto(pin))
}
return ListRoomPinsResult{Pins: pins, Total: resp.GetTotal()}, nil
}
func (c *GRPCClient) CreateRoomPin(ctx context.Context, req CreateRoomPinRequest) (RoomPin, error) {
resp, err := c.client.AdminCreateRoomPin(ctx, &roomv1.AdminCreateRoomPinRequest{
Meta: requestMeta(ctx, strings.TrimSpace(req.RoomID), int64(req.AdminID), "admin-create-room-pin"),
RoomId: strings.TrimSpace(req.RoomID),
Weight: req.Weight,
DurationDays: req.DurationDays,
AdminId: req.AdminID,
})
if err != nil {
return RoomPin{}, err
}
return roomPinFromProto(resp.GetPin()), nil
}
func (c *GRPCClient) CancelRoomPin(ctx context.Context, req CancelRoomPinRequest) (RoomPin, error) {
resp, err := c.client.AdminCancelRoomPin(ctx, &roomv1.AdminCancelRoomPinRequest{
Meta: requestMeta(ctx, "", int64(req.AdminID), "admin-cancel-room-pin"),
PinId: req.PinID,
AdminId: req.AdminID,
})
if err != nil {
return RoomPin{}, err
}
return roomPinFromProto(resp.GetPin()), nil
}
func (c *GRPCClient) CloseRoom(ctx context.Context, req CloseRoomRequest) (*CloseRoomResult, error) {
return c.setRoomLifecycle(ctx, req.RequestID, req.RoomID, req.ActorUserID, firstNonEmpty(req.Reason, "admin_closed"))
}
func (c *GRPCClient) ReopenRoom(ctx context.Context, req ReopenRoomRequest) (*CloseRoomResult, error) {
return c.setRoomLifecycle(ctx, req.RequestID, req.RoomID, req.ActorUserID, "admin_reopen")
}
func (c *GRPCClient) setRoomLifecycle(ctx context.Context, rawRequestID string, rawRoomID string, actorUserID int64, rawReason string) (*CloseRoomResult, error) {
if c == nil || c.client == nil {
return nil, fmt.Errorf("room service client is not configured")
}
roomID := strings.TrimSpace(rawRoomID)
reason := strings.TrimSpace(rawReason)
if roomID == "" || actorUserID <= 0 || reason == "" {
return nil, fmt.Errorf("room lifecycle request is incomplete")
}
requestID := strings.TrimSpace(rawRequestID)
if requestID == "" {
requestID = fmt.Sprintf("admin-room-lifecycle-%s-%d", roomID, time.Now().UnixMilli())
}
resp, err := c.client.CloseRoom(ctx, &roomv1.CloseRoomRequest{
Meta: requestMeta(ctx, roomID, actorUserID, requestID+":"+reason),
Reason: reason,
})
if err != nil {
return nil, err
}
return closeRoomResultFromCommand(resp.GetResult(), resp.GetRoom()), nil
}
func closeRoomResultFromCommand(result *roomv1.CommandResult, room *roomv1.RoomSnapshot) *CloseRoomResult {
return &CloseRoomResult{
Applied: result.GetApplied(),
RoomVersion: result.GetRoomVersion(),
ServerTimeMs: result.GetServerTimeMs(),
RoomStatus: room.GetStatus(),
OnlineUserNum: len(room.GetOnlineUsers()),
}
}
func commandID(requestID string, reason string, roomID string) string {
base := strings.TrimSpace(requestID)
if base == "" {
base = fmt.Sprintf("admin-room-lifecycle-%d", time.Now().UnixMilli())
}
value := base + ":" + strings.TrimSpace(reason) + ":" + strings.TrimSpace(roomID)
if len(value) <= 128 {
return value
}
return value[:128]
}
func requestMeta(ctx context.Context, roomID string, actorUserID int64, seed string) *roomv1.RequestMeta {
requestID := strings.TrimSpace(seed)
if requestID == "" {
requestID = fmt.Sprintf("admin-room-%d", time.Now().UnixMilli())
}
return &roomv1.RequestMeta{
RequestId: requestID,
CommandId: commandID(requestID, "admin", roomID),
ActorUserId: actorUserID,
RoomId: strings.TrimSpace(roomID),
AppCode: appctx.FromContext(ctx),
SentAtMs: time.Now().UnixMilli(),
}
}
func roomFromProto(item *roomv1.AdminRoomListItem) Room {
if item == nil {
return Room{}
}
return Room{
RoomID: item.GetRoomId(),
RoomShortID: item.GetRoomShortId(),
VisibleRegionID: item.GetVisibleRegionId(),
OwnerUserID: item.GetOwnerUserId(),
Title: item.GetTitle(),
CoverURL: item.GetCoverUrl(),
Mode: item.GetMode(),
Status: item.GetStatus(),
CloseReason: item.GetCloseReason(),
ClosedByAdminID: item.GetClosedByAdminId(),
ClosedByAdminName: item.GetClosedByAdminName(),
ClosedAtMS: item.GetClosedAtMs(),
Heat: item.GetHeat(),
OnlineCount: item.GetOnlineCount(),
SeatCount: item.GetSeatCount(),
OccupiedSeatCount: item.GetOccupiedSeatCount(),
SortScore: item.GetSortScore(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
}
func roomRocketConfigFromProto(input *roomv1.AdminRoomRocketConfig) RoomRocketConfig {
if input == nil {
return RoomRocketConfig{}
}
return RoomRocketConfig{
AppCode: input.GetAppCode(),
Enabled: input.GetEnabled(),
ConfigVersion: input.GetConfigVersion(),
FuelSource: input.GetFuelSource(),
LaunchDelayMS: input.GetLaunchDelayMs(),
BroadcastEnabled: input.GetBroadcastEnabled(),
BroadcastScope: input.GetBroadcastScope(),
BroadcastDelayMS: input.GetBroadcastDelayMs(),
RewardStackPolicy: input.GetRewardStackPolicy(),
Levels: roomRocketLevelsFromProto(input.GetLevels()),
GiftFuelRules: roomRocketFuelRulesFromProto(input.GetGiftFuelRules()),
UpdatedByAdminID: input.GetUpdatedByAdminId(),
CreatedAtMS: input.GetCreatedAtMs(),
UpdatedAtMS: input.GetUpdatedAtMs(),
}
}
func roomSeatConfigFromProto(input *roomv1.AdminRoomSeatConfig) RoomSeatConfig {
if input == nil {
return RoomSeatConfig{}
}
return RoomSeatConfig{
CandidateSeatCounts: input.GetCandidateSeatCounts(),
AllowedSeatCounts: input.GetAllowedSeatCounts(),
DefaultSeatCount: input.GetDefaultSeatCount(),
UpdatedAtMS: input.GetUpdatedAtMs(),
}
}
func roomPinFromProto(input *roomv1.AdminRoomPin) RoomPin {
if input == nil {
return RoomPin{}
}
room := input.GetRoom()
return RoomPin{
ID: input.GetId(),
VisibleRegionID: input.GetVisibleRegionId(),
RoomID: input.GetRoomId(),
Weight: input.GetWeight(),
Status: input.GetStatus(),
PinnedAtMS: input.GetPinnedAtMs(),
ExpiresAtMS: input.GetExpiresAtMs(),
CancelledAtMS: input.GetCancelledAtMs(),
CreatedByAdminID: input.GetCreatedByAdminId(),
CancelledByAdminID: input.GetCancelledByAdminId(),
CreatedAtMS: input.GetCreatedAtMs(),
UpdatedAtMS: input.GetUpdatedAtMs(),
Room: RoomPinRoom{
RoomID: room.GetRoomId(),
RoomShortID: room.GetRoomShortId(),
VisibleRegionID: room.GetVisibleRegionId(),
OwnerUserID: room.GetOwnerUserId(),
Title: room.GetTitle(),
CoverURL: room.GetCoverUrl(),
Status: room.GetStatus(),
},
}
}
func roomRocketConfigToProto(input RoomRocketConfig) *roomv1.AdminRoomRocketConfig {
return &roomv1.AdminRoomRocketConfig{
Enabled: input.Enabled,
FuelSource: input.FuelSource,
LaunchDelayMs: input.LaunchDelayMS,
BroadcastEnabled: input.BroadcastEnabled,
BroadcastScope: input.BroadcastScope,
BroadcastDelayMs: input.BroadcastDelayMS,
RewardStackPolicy: input.RewardStackPolicy,
Levels: roomRocketLevelsToProto(input.Levels),
GiftFuelRules: roomRocketFuelRulesToProto(input.GiftFuelRules),
}
}
func roomRocketLevelsFromProto(input []*roomv1.RoomRocketLevel) []RoomRocketLevelConfig {
out := make([]RoomRocketLevelConfig, 0, len(input))
for _, level := range input {
if level == nil {
continue
}
out = append(out, RoomRocketLevelConfig{
Level: level.GetLevel(),
FuelThreshold: level.GetFuelThreshold(),
CoverURL: level.GetCoverUrl(),
AnimationURL: level.GetAnimationUrl(),
LaunchAnimationURL: level.GetLaunchAnimationUrl(),
LaunchedImageURL: level.GetLaunchedImageUrl(),
InRoomRewards: roomRocketRewardsFromProto(level.GetInRoomRewards()),
Top1Rewards: roomRocketRewardsFromProto(level.GetTop1Rewards()),
IgniterRewards: roomRocketRewardsFromProto(level.GetIgniterRewards()),
})
}
return out
}
func roomRocketLevelsToProto(input []RoomRocketLevelConfig) []*roomv1.RoomRocketLevel {
out := make([]*roomv1.RoomRocketLevel, 0, len(input))
for _, level := range input {
out = append(out, &roomv1.RoomRocketLevel{
Level: level.Level,
FuelThreshold: level.FuelThreshold,
CoverUrl: level.CoverURL,
AnimationUrl: level.AnimationURL,
LaunchAnimationUrl: level.LaunchAnimationURL,
LaunchedImageUrl: level.LaunchedImageURL,
InRoomRewards: roomRocketRewardsToProto(level.InRoomRewards),
Top1Rewards: roomRocketRewardsToProto(level.Top1Rewards),
IgniterRewards: roomRocketRewardsToProto(level.IgniterRewards),
})
}
return out
}
func roomRocketRewardsFromProto(input []*roomv1.RoomRocketRewardItem) []RoomRocketRewardItem {
out := make([]RoomRocketRewardItem, 0, len(input))
for _, reward := range input {
if reward == nil {
continue
}
out = append(out, RoomRocketRewardItem{
RewardItemID: reward.GetRewardItemId(),
ResourceGroupID: reward.GetResourceGroupId(),
Weight: reward.GetWeight(),
DisplayName: reward.GetDisplayName(),
IconURL: reward.GetIconUrl(),
})
}
return out
}
func roomRocketRewardsToProto(input []RoomRocketRewardItem) []*roomv1.RoomRocketRewardItem {
out := make([]*roomv1.RoomRocketRewardItem, 0, len(input))
for _, reward := range input {
out = append(out, &roomv1.RoomRocketRewardItem{
RewardItemId: reward.RewardItemID,
ResourceGroupId: reward.ResourceGroupID,
Weight: reward.Weight,
DisplayName: reward.DisplayName,
IconUrl: reward.IconURL,
})
}
return out
}
func roomRocketFuelRulesFromProto(input []*roomv1.RoomRocketGiftFuelRule) []GiftFuelRuleConfig {
out := make([]GiftFuelRuleConfig, 0, len(input))
for _, rule := range input {
if rule == nil {
continue
}
out = append(out, GiftFuelRuleConfig{
RuleID: rule.GetRuleId(),
GiftID: rule.GetGiftId(),
GiftTypeCode: rule.GetGiftTypeCode(),
MultiplierPPM: rule.GetMultiplierPpm(),
FixedFuel: rule.GetFixedFuel(),
Excluded: rule.GetExcluded(),
})
}
return out
}
func roomRocketFuelRulesToProto(input []GiftFuelRuleConfig) []*roomv1.RoomRocketGiftFuelRule {
out := make([]*roomv1.RoomRocketGiftFuelRule, 0, len(input))
for _, rule := range input {
out = append(out, &roomv1.RoomRocketGiftFuelRule{
RuleId: rule.RuleID,
GiftId: rule.GiftID,
GiftTypeCode: rule.GiftTypeCode,
MultiplierPpm: rule.MultiplierPPM,
FixedFuel: rule.FixedFuel,
Excluded: rule.Excluded,
})
}
return out
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return value
}
}
return ""
}