2026-06-11 13:33:44 +08:00

240 lines
8.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gamemanagement
import (
"fmt"
"strings"
"time"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/middleware"
gamev1 "hyapp.local/api/proto/game/v1"
"github.com/gin-gonic/gin"
)
type platformRequest struct {
PlatformCode string `json:"platformCode"`
PlatformName string `json:"platformName"`
Status string `json:"status"`
APIBaseURL string `json:"apiBaseUrl"`
// AdapterType 只允许服务端白名单中的值,避免后台误填导致回调走错协议。
AdapterType string `json:"adapterType"`
// CallbackSecret 只有提交非空时才覆盖旧密钥,编辑其它字段时前端传空即可。
CallbackSecret string `json:"callbackSecret"`
CallbackIPWhitelist []string `json:"callbackIpWhitelist"`
// AdapterConfigJSON 是厂商扩展配置,保持 JSON 字符串透传给 game-service。
AdapterConfigJSON string `json:"adapterConfigJson"`
SortOrder int32 `json:"sortOrder"`
}
type catalogRequest struct {
GameID string `json:"gameId"`
PlatformCode string `json:"platformCode"`
ProviderGameID string `json:"providerGameId"`
GameName string `json:"gameName"`
Category string `json:"category"`
IconURL string `json:"iconUrl"`
CoverURL string `json:"coverUrl"`
LaunchMode string `json:"launchMode"`
Orientation string `json:"orientation"`
SafeHeight int32 `json:"safeHeight"`
MinCoin int64 `json:"minCoin"`
Status string `json:"status"`
SortOrder int32 `json:"sortOrder"`
Tags []string `json:"tags"`
}
type statusRequest struct {
Status string `json:"status"`
}
type diceConfigRequest struct {
Status string `json:"status"`
StakeOptions []diceStakeReq `json:"stakeOptions"`
FeeBPS int32 `json:"feeBps"`
PoolBPS int32 `json:"poolBps"`
MinPlayers int32 `json:"minPlayers"`
MaxPlayers int32 `json:"maxPlayers"`
RobotEnabled bool `json:"robotEnabled"`
RobotMatchWaitMS int64 `json:"robotMatchWaitMs"`
}
type diceStakeReq struct {
StakeCoin int64 `json:"stakeCoin"`
Enabled bool `json:"enabled"`
SortOrder int32 `json:"sortOrder"`
}
type dicePoolAdjustRequest struct {
AmountCoin int64 `json:"amountCoin"`
Direction string `json:"direction"`
Reason string `json:"reason"`
}
type diceGenerateRobotsRequest struct {
Count int32 `json:"count"`
NicknamePrefix string `json:"nicknamePrefix"`
NicknameLanguage string `json:"nicknameLanguage"`
AvatarURLs []string `json:"avatarUrls"`
Country string `json:"country"`
Gender string `json:"gender"`
}
type diceRobotStatusRequest struct {
Status string `json:"status"`
}
type roomRPSConfigRequest struct {
Status string `json:"status"`
ChallengeTimeoutMS int64 `json:"challengeTimeoutMs"`
RevealCountdownMS int64 `json:"revealCountdownMs"`
StakeGifts []roomRPSStakeGiftReq `json:"stakeGifts"`
}
type roomRPSStakeGiftReq struct {
GiftID int64 `json:"giftId"`
Enabled bool `json:"enabled"`
SortOrder int32 `json:"sortOrder"`
}
func (r platformRequest) toProto(platformCode string) *gamev1.GamePlatform {
if platformCode == "" {
platformCode = r.PlatformCode
}
return &gamev1.GamePlatform{
PlatformCode: strings.TrimSpace(platformCode),
PlatformName: strings.TrimSpace(r.PlatformName),
Status: strings.TrimSpace(r.Status),
ApiBaseUrl: strings.TrimSpace(r.APIBaseURL),
AdapterType: strings.TrimSpace(r.AdapterType),
CallbackSecret: strings.TrimSpace(r.CallbackSecret),
CallbackIpWhitelist: compactTags(r.CallbackIPWhitelist),
AdapterConfigJson: strings.TrimSpace(r.AdapterConfigJSON),
SortOrder: r.SortOrder,
}
}
func (r catalogRequest) toProto(gameID string) *gamev1.GameCatalogItem {
if gameID == "" {
gameID = r.GameID
}
return &gamev1.GameCatalogItem{
GameId: strings.TrimSpace(gameID),
PlatformCode: strings.TrimSpace(r.PlatformCode),
ProviderGameId: strings.TrimSpace(r.ProviderGameID),
GameName: strings.TrimSpace(r.GameName),
Category: strings.TrimSpace(r.Category),
IconUrl: strings.TrimSpace(r.IconURL),
CoverUrl: strings.TrimSpace(r.CoverURL),
LaunchMode: strings.TrimSpace(r.LaunchMode),
Orientation: strings.TrimSpace(r.Orientation),
SafeHeight: r.SafeHeight,
MinCoin: r.MinCoin,
Status: strings.TrimSpace(r.Status),
SortOrder: r.SortOrder,
Tags: compactTags(r.Tags),
}
}
func (r diceConfigRequest) toProto(gameID string) *gamev1.DiceConfig {
options := make([]*gamev1.DiceStakeOption, 0, len(r.StakeOptions))
for _, option := range r.StakeOptions {
options = append(options, &gamev1.DiceStakeOption{
StakeCoin: option.StakeCoin,
Enabled: option.Enabled,
SortOrder: option.SortOrder,
})
}
return &gamev1.DiceConfig{
GameId: strings.TrimSpace(gameID),
Status: strings.TrimSpace(r.Status),
StakeOptions: options,
FeeBps: r.FeeBPS,
PoolBps: r.PoolBPS,
MinPlayers: r.MinPlayers,
MaxPlayers: r.MaxPlayers,
RobotEnabled: r.RobotEnabled,
RobotMatchWaitMs: r.RobotMatchWaitMS,
}
}
func (r roomRPSConfigRequest) toProto() (*gamev1.RoomRPSConfig, error) {
status := strings.TrimSpace(r.Status)
if status == "" {
// 业务功能配置默认打开;后台没传 status 时不能默认为关闭,否则新环境上线后 App 入口会被静默隐藏。
status = "active"
}
timeoutMS := r.ChallengeTimeoutMS
if timeoutMS <= 0 {
// 无人应战超时使用产品默认 10 分钟0 或负数按“未填写”处理,不允许保存成立刻过期。
timeoutMS = 10 * 60 * 1000
}
revealMS := r.RevealCountdownMS
if revealMS <= 0 {
// 揭晓倒计时使用产品默认 3 秒;客户端动画和 IM 倒计时都依赖这个稳定默认值。
revealMS = 3 * 1000
}
if len(r.StakeGifts) != 4 {
// 房内猜拳礼物档位是产品固定四档,少一档会导致 Flutter 面板缺位,多一档会导致后台和客户端展示不一致。
return nil, fmt.Errorf("房内猜拳礼物档位必须配置 4 档")
}
gifts := make([]*gamev1.RoomRPSStakeGift, 0, len(r.StakeGifts))
seen := make(map[int64]struct{}, len(r.StakeGifts))
for index, gift := range r.StakeGifts {
if gift.GiftID <= 0 {
// gift_id 必须是后台真实礼物 ID0 不能表示“空档位”,空档位应该通过 enabled=false 表达。
return nil, fmt.Errorf("第 %d 档礼物 ID 不正确", index+1)
}
if _, ok := seen[gift.GiftID]; ok {
// 同一个礼物不能重复配置成多档,否则结算金额和排序展示会变成同一个 gift_id 的多份事实。
return nil, fmt.Errorf("第 %d 档礼物 ID 重复", index+1)
}
seen[gift.GiftID] = struct{}{}
sortOrder := gift.SortOrder
if sortOrder <= 0 {
// 未显式给排序时按提交顺序落库,保证前端拖拽顺序和运行时展示顺序一致。
sortOrder = int32(index + 1)
}
gifts = append(gifts, &gamev1.RoomRPSStakeGift{
GiftId: gift.GiftID,
Enabled: gift.Enabled,
SortOrder: sortOrder,
})
}
return &gamev1.RoomRPSConfig{
// 房内猜拳和独立猜拳必须用不同 game_id这里固定 room_rps防止后台误把配置写到独立猜拳。
GameId: "room_rps",
Status: status,
ChallengeTimeoutMs: timeoutMS,
RevealCountdownMs: revealMS,
StakeGifts: gifts,
}, nil
}
func compactTags(tags []string) []string {
out := make([]string, 0, len(tags))
seen := make(map[string]struct{}, len(tags))
for _, tag := range tags {
value := strings.TrimSpace(tag)
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
out = append(out, value)
}
return out
}
func requestMeta(c *gin.Context) *gamev1.RequestMeta {
return &gamev1.RequestMeta{
RequestId: middleware.CurrentRequestID(c),
Caller: "admin-server",
ActorUserId: int64(middleware.CurrentUserID(c)),
SentAtMs: time.Now().UnixMilli(),
AppCode: appctx.FromContext(c.Request.Context()),
}
}