370 lines
12 KiB
Go
370 lines
12 KiB
Go
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)
|
||
meta := requestMeta(c)
|
||
appCode := meta.GetAppCode()
|
||
if appCode == "" {
|
||
appCode = "lalu"
|
||
meta.AppCode = appCode
|
||
}
|
||
actorUserID := int64(middleware.CurrentUserID(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
|
||
}
|
||
if err := h.initializeGeneratedRobotAppearance(c.Request.Context(), requestID, appCode, actorUserID, created.UserID); err != nil {
|
||
response.BadRequest(c, "初始化机器人装扮失败: "+err.Error())
|
||
return
|
||
}
|
||
userIDs = append(userIDs, created.UserID)
|
||
}
|
||
resp, err := h.game.RegisterDiceRobots(c.Request.Context(), &gamev1.RegisterDiceRobotsRequest{
|
||
Meta: meta,
|
||
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
|
||
}
|