package gamemanagement import ( "crypto/rand" "encoding/hex" "fmt" "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) AdjustDicePool(c *gin.Context) { gameID := strings.TrimSpace(c.Param("game_id")) if gameID == "" { response.BadRequest(c, "游戏 ID 参数不正确") return } var req dicePoolAdjustRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "奖池调整参数不正确") return } resp, err := h.game.AdjustDicePool(c.Request.Context(), &gamev1.AdjustDicePoolRequest{ Meta: requestMeta(c), GameId: gameID, 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-dice-pool", "game_self_game_pools", gameID, fmt.Sprintf("%s:%d", req.Direction, req.AmountCoin)) response.OK(c, gin.H{"adjustment": dicePoolAdjustmentFromProto(resp.GetAdjustment()), "config": diceConfigFromProto(resp.GetConfig())}) } 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 = 20 } if req.Count > 200 { req.Count = 200 } nicknameLanguage := normalizeRobotNicknameLanguage(req.NicknameLanguage) country := strings.TrimSpace(req.Country) if country == "" { country = "SA" } userIDs := make([]int64, 0, req.Count) requestID := middleware.CurrentRequestID(c) actorID := middleware.CurrentUserID(c) avatarCache := make(map[string]string) for index := int32(0); index < req.Count; index++ { // 批量机器人必须一次性写齐完整资料;同一请求内相同头像源只转存一次,避免 200 个账号重复下载上传同一张图。 avatarSource := robotAvatarSource(int(index), req.AvatarURLs) avatarURL := avatarCache[avatarSource] if avatarURL == "" { var err error avatarURL, err = h.uploadRobotAvatarFromSource(c.Request.Context(), actorID, avatarSource) if err != nil { response.BadRequest(c, "上传机器人头像失败: "+err.Error()) return } avatarCache[avatarSource] = avatarURL } created, err := h.user.QuickCreateAccount(c.Request.Context(), userclient.QuickCreateAccountRequest{ RequestID: requestID, Caller: "admin-server", Password: randomRobotPassword(), Username: robotNickname(int(index), nicknameLanguage), Avatar: avatarURL, Gender: robotGender(int(index), req.Gender), Country: country, DeviceID: "game-robot-" + randomRobotHex(8), Source: "game_robot", InstallChannel: "admin", Platform: "android", Language: robotAccountLanguage(nicknameLanguage), 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", "dice", 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 pickString(values []string, index int) string { if len(values) == 0 { return "" } return strings.TrimSpace(values[index%len(values)]) } func parseInt64Param(raw string) (int64, error) { var value int64 _, err := fmt.Sscan(strings.TrimSpace(raw), &value) return value, err }