package gamemanagement import ( "crypto/rand" "encoding/hex" "fmt" "strconv" "strings" "hyapp-admin-server/internal/integration/userclient" "hyapp-admin-server/internal/middleware" "hyapp-admin-server/internal/response" gamev1 "hyapp.local/api/proto/game/v1" "github.com/gin-gonic/gin" ) func (h *Handler) ListSelfGames(c *gin.Context) { resp, err := h.game.ListSelfGames(c.Request.Context(), &gamev1.ListSelfGamesRequest{Meta: requestMeta(c)}) if err != nil { response.ServerError(c, "获取自研游戏失败") return } items := make([]diceConfigDTO, 0, len(resp.GetGames())) for _, item := range resp.GetGames() { items = append(items, diceConfigFromProto(item)) } response.OK(c, gin.H{"items": items, "serverTimeMs": resp.GetServerTimeMs()}) } func (h *Handler) UpdateDiceConfig(c *gin.Context) { gameID := strings.TrimSpace(c.Param("game_id")) if gameID == "" { response.BadRequest(c, "游戏 ID 参数不正确") return } var req diceConfigRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "骰子配置参数不正确") return } resp, err := h.game.UpdateDiceConfig(c.Request.Context(), &gamev1.UpdateDiceConfigRequest{ Meta: requestMeta(c), Config: req.toProto(gameID), }) if err != nil { response.BadRequest(c, err.Error()) return } item := diceConfigFromProto(resp.GetConfig()) h.auditLog(c, "update-dice-config", "game_self_game_configs", item.GameID, item.Status) response.OK(c, item) } func (h *Handler) GetSelfGameNewUserPolicy(c *gin.Context) { gameID := strings.TrimSpace(c.Param("game_id")) if gameID == "" { response.BadRequest(c, "游戏 ID 参数不正确") return } resp, err := h.game.GetSelfGameNewUserPolicy(c.Request.Context(), &gamev1.GetSelfGameNewUserPolicyRequest{ Meta: requestMeta(c), GameId: gameID, }) if err != nil { response.ServerError(c, "获取新手保护策略失败") return } response.OK(c, selfGameNewUserPolicyFromProto(resp.GetPolicy())) } func (h *Handler) UpdateSelfGameNewUserPolicy(c *gin.Context) { gameID := strings.TrimSpace(c.Param("game_id")) if gameID == "" { response.BadRequest(c, "游戏 ID 参数不正确") return } var req selfGameNewUserPolicyRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "新手保护策略参数不正确") return } resp, err := h.game.UpdateSelfGameNewUserPolicy(c.Request.Context(), &gamev1.UpdateSelfGameNewUserPolicyRequest{ Meta: requestMeta(c), Policy: req.toProto(gameID), }) if err != nil { response.BadRequest(c, err.Error()) return } item := selfGameNewUserPolicyFromProto(resp.GetPolicy()) h.auditLog(c, "update-self-game-new-user-policy", "game_self_game_new_user_policies", item.GameID, item.StrategyLevel) response.OK(c, item) } func (h *Handler) ListSelfGameStakePools(c *gin.Context) { gameID := strings.TrimSpace(c.Param("game_id")) if gameID == "" { response.BadRequest(c, "游戏 ID 参数不正确") return } resp, err := h.game.ListSelfGameStakePools(c.Request.Context(), &gamev1.ListSelfGameStakePoolsRequest{ Meta: requestMeta(c), GameId: gameID, }) if err != nil { response.ServerError(c, "获取档位奖池失败") return } items := make([]selfGameStakePoolDTO, 0, len(resp.GetPools())) for _, pool := range resp.GetPools() { items = append(items, selfGameStakePoolFromProto(pool)) } response.OK(c, gin.H{"items": items, "serverTimeMs": resp.GetServerTimeMs()}) } func (h *Handler) UpdateSelfGameStakePool(c *gin.Context) { gameID := strings.TrimSpace(c.Param("game_id")) stakeCoin, ok := parseStakeCoinParam(c) if gameID == "" || !ok { response.BadRequest(c, "档位奖池参数不正确") return } var req selfGameStakePoolRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "档位奖池配置参数不正确") return } resp, err := h.game.UpdateSelfGameStakePool(c.Request.Context(), &gamev1.UpdateSelfGameStakePoolRequest{ Meta: requestMeta(c), Pool: req.toProto(gameID, stakeCoin), }) if err != nil { response.BadRequest(c, err.Error()) return } item := selfGameStakePoolFromProto(resp.GetPool()) h.auditLog(c, "update-self-game-stake-pool", "game_self_game_stake_pools", fmt.Sprintf("%s:%d", gameID, stakeCoin), item.Status) response.OK(c, item) } func (h *Handler) AdjustSelfGameStakePool(c *gin.Context) { gameID := strings.TrimSpace(c.Param("game_id")) stakeCoin, ok := parseStakeCoinParam(c) if gameID == "" || !ok { response.BadRequest(c, "档位奖池参数不正确") return } var req dicePoolAdjustRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "档位奖池调整参数不正确") return } resp, err := h.game.AdjustSelfGameStakePool(c.Request.Context(), &gamev1.AdjustSelfGameStakePoolRequest{ Meta: requestMeta(c), GameId: gameID, StakeCoin: stakeCoin, AmountCoin: req.AmountCoin, Direction: strings.TrimSpace(req.Direction), Reason: strings.TrimSpace(req.Reason), }) if err != nil { response.BadRequest(c, err.Error()) return } h.auditLog(c, "adjust-self-game-stake-pool", "game_self_game_stake_pools", fmt.Sprintf("%s:%d", gameID, stakeCoin), fmt.Sprintf("%s:%d", req.Direction, req.AmountCoin)) response.OK(c, gin.H{"adjustment": dicePoolAdjustmentFromProto(resp.GetAdjustment()), "config": diceConfigFromProto(resp.GetConfig())}) } func parseStakeCoinParam(c *gin.Context) (int64, bool) { stakeCoin, err := strconv.ParseInt(strings.TrimSpace(c.Param("stake_coin")), 10, 64) if err != nil || stakeCoin <= 0 { return 0, false } return stakeCoin, true } func (h *Handler) ListDiceRobots(c *gin.Context) { pageSize := int32(parsePositiveInt(firstQuery(c, "pageSize", "page_size"), 50)) requestID := middleware.CurrentRequestID(c) resp, err := h.game.ListDiceRobots(c.Request.Context(), &gamev1.ListDiceRobotsRequest{ Meta: requestMeta(c), GameId: strings.TrimSpace(firstQuery(c, "gameId", "game_id")), Status: strings.TrimSpace(c.Query("status")), PageSize: pageSize, Cursor: strings.TrimSpace(c.Query("cursor")), }) if err != nil { response.ServerError(c, "获取全站机器人失败") return } items := make([]diceRobotDTO, 0, len(resp.GetRobots())) for _, item := range resp.GetRobots() { items = append(items, diceRobotFromProto(item)) } if err := h.enrichDiceRobotProfiles(c.Request.Context(), requestID, items); err != nil { response.ServerError(c, "获取机器人用户资料失败") return } response.OK(c, gin.H{"items": items, "nextCursor": resp.GetNextCursor(), "pageSize": pageSize, "serverTimeMs": resp.GetServerTimeMs()}) } func (h *Handler) GenerateDiceRobots(c *gin.Context) { if h.user == nil { response.ServerError(c, "用户服务不可用") return } var req diceGenerateRobotsRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "机器人生成参数不正确") return } if req.Count <= 0 { req.Count = defaultRobotGenerateCount } if req.Count > maxRobotGenerateCount { req.Count = maxRobotGenerateCount } country := strings.TrimSpace(req.Country) if country == "" { country = "SA" } if h.robotProfiles == nil { response.ServerError(c, "likei 机器人资料源未配置") return } // 新机器人资料只从 likei 线上用户库随机抽取;后台不再使用本地昵称池、随机头像站或 COS 转存, // 这样每次批量创建都面对真实用户资料池,避免少量固定预设反复出现在匹配列表里。 profiles, err := h.robotProfiles.RandomRobotProfiles(c.Request.Context(), int(req.Count)) if err != nil { response.BadRequest(c, "获取 likei 机器人资料失败: "+err.Error()) return } if len(profiles) < int(req.Count) { response.BadRequest(c, "likei 机器人资料数量不足") return } userIDs := make([]int64, 0, req.Count) requestID := middleware.CurrentRequestID(c) accountLanguage := robotAccountLanguage(req.NicknameLanguage) for index := int32(0); index < req.Count; index++ { profile := profiles[index] created, err := h.user.QuickCreateAccount(c.Request.Context(), userclient.QuickCreateAccountRequest{ RequestID: requestID, Caller: "admin-server", Password: randomRobotPassword(), Username: profile.Nickname, Avatar: profile.Avatar, Gender: robotGender(int(index), req.Gender), Country: country, DeviceID: "game-robot-" + randomRobotHex(8), Source: "game_robot", InstallChannel: "admin", Platform: "android", Language: accountLanguage, Timezone: "UTC", }) if err != nil { response.BadRequest(c, "创建机器人用户失败: "+err.Error()) return } userIDs = append(userIDs, created.UserID) } resp, err := h.game.RegisterDiceRobots(c.Request.Context(), &gamev1.RegisterDiceRobotsRequest{ Meta: requestMeta(c), GameId: strings.TrimSpace(firstQuery(c, "gameId", "game_id")), UserIds: userIDs, }) if err != nil { response.BadRequest(c, err.Error()) return } items := make([]diceRobotDTO, 0, len(resp.GetRobots())) for _, item := range resp.GetRobots() { items = append(items, diceRobotFromProto(item)) } if err := h.enrichDiceRobotProfiles(c.Request.Context(), requestID, items); err != nil { response.ServerError(c, "获取机器人用户资料失败") return } h.auditLog(c, "generate-dice-robots", "game_self_game_robots", "universal", fmt.Sprintf("count=%d", len(userIDs))) response.OK(c, gin.H{"created": len(userIDs), "items": items}) } func (h *Handler) SetDiceRobotStatus(c *gin.Context) { gameID := strings.TrimSpace(firstQuery(c, "gameId", "game_id")) if gameID == "" { gameID = "dice" } userID, err := parseInt64Param(c.Param("user_id")) if err != nil || userID <= 0 { response.BadRequest(c, "机器人用户 ID 参数不正确") return } var req diceRobotStatusRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "机器人状态参数不正确") return } resp, err := h.game.SetDiceRobotStatus(c.Request.Context(), &gamev1.SetDiceRobotStatusRequest{ Meta: requestMeta(c), GameId: gameID, UserId: userID, Status: strings.TrimSpace(req.Status), }) if err != nil { response.BadRequest(c, err.Error()) return } item := diceRobotFromProto(resp.GetRobot()) h.auditLog(c, "set-dice-robot-status", "game_self_game_robots", item.UserID, item.Status) response.OK(c, item) } // DeleteDiceRobot 删除当前自研游戏的机器人登记,后台只移除可匹配机器人池,不触碰真实用户资料。 func (h *Handler) DeleteDiceRobot(c *gin.Context) { gameID := strings.TrimSpace(firstQuery(c, "gameId", "game_id")) if gameID == "" { gameID = "dice" } userID, err := parseInt64Param(c.Param("user_id")) if err != nil || userID <= 0 { response.BadRequest(c, "机器人用户 ID 参数不正确") return } // 后台删除只撤销 game-service 的机器人登记;真实用户仍保留在 user-service,历史对局和钱包流水可以继续按 user_id 追溯。 if _, err := h.game.DeleteDiceRobot(c.Request.Context(), &gamev1.DeleteDiceRobotRequest{ Meta: requestMeta(c), GameId: gameID, UserId: userID, }); err != nil { response.BadRequest(c, err.Error()) return } h.auditLog(c, "delete-dice-robot", "game_self_game_robots", fmt.Sprintf("%d", userID), gameID) response.OK(c, gin.H{"deleted": true}) } func randomRobotPassword() string { return "Robot#" + randomRobotHex(12) } func randomRobotHex(size int) string { buf := make([]byte, size) if _, err := rand.Read(buf); err != nil { return "fallback" } return hex.EncodeToString(buf) } func parseInt64Param(raw string) (int64, error) { var value int64 _, err := fmt.Sscan(strings.TrimSpace(raw), &value) return value, err }