2026-05-25 15:45:57 +08:00

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)
GetRoomTreasureConfig(ctx context.Context) (RoomTreasureConfig, error)
UpdateRoomTreasureConfig(ctx context.Context, req UpdateRoomTreasureConfigRequest) (RoomTreasureConfig, 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 RoomTreasureConfig struct {
AppCode string
Enabled bool
ConfigVersion int64
EnergySource string
OpenDelayMS int64
BroadcastEnabled bool
BroadcastScope string
BroadcastDelayMS int64
RewardStackPolicy string
Levels []RoomTreasureLevelConfig
GiftEnergyRules []GiftEnergyRuleConfig
UpdatedByAdminID int64
CreatedAtMS int64
UpdatedAtMS int64
}
type RoomTreasureLevelConfig struct {
Level int32
EnergyThreshold int64
CoverURL string
AnimationURL string
OpeningAnimationURL string
OpenedImageURL string
InRoomRewards []RoomTreasureRewardItem
Top1Rewards []RoomTreasureRewardItem
IgniterRewards []RoomTreasureRewardItem
}
type RoomTreasureRewardItem struct {
RewardItemID string
ResourceGroupID int64
Weight int64
DisplayName string
IconURL string
}
type GiftEnergyRuleConfig struct {
RuleID string
GiftID string
GiftTypeCode string
MultiplierPPM int64
FixedEnergy int64
Excluded bool
}
type UpdateRoomTreasureConfigRequest struct {
Config RoomTreasureConfig
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) GetRoomTreasureConfig(ctx context.Context) (RoomTreasureConfig, error) {
resp, err := c.queryClient.AdminGetRoomTreasureConfig(ctx, &roomv1.AdminGetRoomTreasureConfigRequest{
Meta: requestMeta(ctx, "", 0, "admin-get-room-treasure-config"),
})
if err != nil {
return RoomTreasureConfig{}, err
}
return roomTreasureConfigFromProto(resp.GetConfig()), nil
}
func (c *GRPCClient) UpdateRoomTreasureConfig(ctx context.Context, req UpdateRoomTreasureConfigRequest) (RoomTreasureConfig, error) {
resp, err := c.client.AdminUpdateRoomTreasureConfig(ctx, &roomv1.AdminUpdateRoomTreasureConfigRequest{
Meta: requestMeta(ctx, "", req.AdminID, "admin-update-room-treasure-config"),
Config: roomTreasureConfigToProto(req.Config),
AdminId: req.AdminID,
})
if err != nil {
return RoomTreasureConfig{}, err
}
return roomTreasureConfigFromProto(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 roomTreasureConfigFromProto(input *roomv1.AdminRoomTreasureConfig) RoomTreasureConfig {
if input == nil {
return RoomTreasureConfig{}
}
return RoomTreasureConfig{
AppCode: input.GetAppCode(),
Enabled: input.GetEnabled(),
ConfigVersion: input.GetConfigVersion(),
EnergySource: input.GetEnergySource(),
OpenDelayMS: input.GetOpenDelayMs(),
BroadcastEnabled: input.GetBroadcastEnabled(),
BroadcastScope: input.GetBroadcastScope(),
BroadcastDelayMS: input.GetBroadcastDelayMs(),
RewardStackPolicy: input.GetRewardStackPolicy(),
Levels: roomTreasureLevelsFromProto(input.GetLevels()),
GiftEnergyRules: roomTreasureEnergyRulesFromProto(input.GetGiftEnergyRules()),
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 roomTreasureConfigToProto(input RoomTreasureConfig) *roomv1.AdminRoomTreasureConfig {
return &roomv1.AdminRoomTreasureConfig{
Enabled: input.Enabled,
EnergySource: input.EnergySource,
OpenDelayMs: input.OpenDelayMS,
BroadcastEnabled: input.BroadcastEnabled,
BroadcastScope: input.BroadcastScope,
BroadcastDelayMs: input.BroadcastDelayMS,
RewardStackPolicy: input.RewardStackPolicy,
Levels: roomTreasureLevelsToProto(input.Levels),
GiftEnergyRules: roomTreasureEnergyRulesToProto(input.GiftEnergyRules),
}
}
func roomTreasureLevelsFromProto(input []*roomv1.RoomTreasureLevel) []RoomTreasureLevelConfig {
out := make([]RoomTreasureLevelConfig, 0, len(input))
for _, level := range input {
if level == nil {
continue
}
out = append(out, RoomTreasureLevelConfig{
Level: level.GetLevel(),
EnergyThreshold: level.GetEnergyThreshold(),
CoverURL: level.GetCoverUrl(),
AnimationURL: level.GetAnimationUrl(),
OpeningAnimationURL: level.GetOpeningAnimationUrl(),
OpenedImageURL: level.GetOpenedImageUrl(),
InRoomRewards: roomTreasureRewardsFromProto(level.GetInRoomRewards()),
Top1Rewards: roomTreasureRewardsFromProto(level.GetTop1Rewards()),
IgniterRewards: roomTreasureRewardsFromProto(level.GetIgniterRewards()),
})
}
return out
}
func roomTreasureLevelsToProto(input []RoomTreasureLevelConfig) []*roomv1.RoomTreasureLevel {
out := make([]*roomv1.RoomTreasureLevel, 0, len(input))
for _, level := range input {
out = append(out, &roomv1.RoomTreasureLevel{
Level: level.Level,
EnergyThreshold: level.EnergyThreshold,
CoverUrl: level.CoverURL,
AnimationUrl: level.AnimationURL,
OpeningAnimationUrl: level.OpeningAnimationURL,
OpenedImageUrl: level.OpenedImageURL,
InRoomRewards: roomTreasureRewardsToProto(level.InRoomRewards),
Top1Rewards: roomTreasureRewardsToProto(level.Top1Rewards),
IgniterRewards: roomTreasureRewardsToProto(level.IgniterRewards),
})
}
return out
}
func roomTreasureRewardsFromProto(input []*roomv1.RoomTreasureRewardItem) []RoomTreasureRewardItem {
out := make([]RoomTreasureRewardItem, 0, len(input))
for _, reward := range input {
if reward == nil {
continue
}
out = append(out, RoomTreasureRewardItem{
RewardItemID: reward.GetRewardItemId(),
ResourceGroupID: reward.GetResourceGroupId(),
Weight: reward.GetWeight(),
DisplayName: reward.GetDisplayName(),
IconURL: reward.GetIconUrl(),
})
}
return out
}
func roomTreasureRewardsToProto(input []RoomTreasureRewardItem) []*roomv1.RoomTreasureRewardItem {
out := make([]*roomv1.RoomTreasureRewardItem, 0, len(input))
for _, reward := range input {
out = append(out, &roomv1.RoomTreasureRewardItem{
RewardItemId: reward.RewardItemID,
ResourceGroupId: reward.ResourceGroupID,
Weight: reward.Weight,
DisplayName: reward.DisplayName,
IconUrl: reward.IconURL,
})
}
return out
}
func roomTreasureEnergyRulesFromProto(input []*roomv1.RoomTreasureGiftEnergyRule) []GiftEnergyRuleConfig {
out := make([]GiftEnergyRuleConfig, 0, len(input))
for _, rule := range input {
if rule == nil {
continue
}
out = append(out, GiftEnergyRuleConfig{
RuleID: rule.GetRuleId(),
GiftID: rule.GetGiftId(),
GiftTypeCode: rule.GetGiftTypeCode(),
MultiplierPPM: rule.GetMultiplierPpm(),
FixedEnergy: rule.GetFixedEnergy(),
Excluded: rule.GetExcluded(),
})
}
return out
}
func roomTreasureEnergyRulesToProto(input []GiftEnergyRuleConfig) []*roomv1.RoomTreasureGiftEnergyRule {
out := make([]*roomv1.RoomTreasureGiftEnergyRule, 0, len(input))
for _, rule := range input {
out = append(out, &roomv1.RoomTreasureGiftEnergyRule{
RuleId: rule.RuleID,
GiftId: rule.GiftID,
GiftTypeCode: rule.GiftTypeCode,
MultiplierPpm: rule.MultiplierPPM,
FixedEnergy: rule.FixedEnergy,
Excluded: rule.Excluded,
})
}
return out
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return value
}
}
return ""
}