2026-06-16 20:47:26 +08:00

359 lines
12 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 (
"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"
}
// likei 是优先资料源;测试服或本地未配置时用随机资料兜底,避免运维为了造机器人账号被外部历史库阻塞。
profiles, err := robotProfilesForGenerate(c.Request.Context(), h.robotProfiles, int(req.Count))
if err != nil {
response.BadRequest(c, "获取机器人资料失败: "+err.Error())
return
}
if len(profiles) < int(req.Count) {
response.BadRequest(c, "机器人资料数量不足")
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]
gender := strings.TrimSpace(req.Gender)
if gender == "" {
gender = strings.TrimSpace(profile.Gender)
}
if gender == "" {
// likei 当前只提供昵称和头像;没有显式性别时保持旧的均匀分布,随机兜底资料会在 profile 中给出真实随机值。
gender = robotGender(int(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: 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
}