222 lines
8.6 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 (
"context"
"errors"
"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"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const gameWhitelistInternalUserIDMinDigits = 15
var errGameWhitelistUserNotFound = errors.New("game whitelist user not found")
type gameWhitelistEnabledRequest struct {
Enabled bool `json:"enabled"`
}
type gameWhitelistUserRequest struct {
// 保留 userId JSON 字段兼容已发布管理端;值允许内部 user_id、默认短号或当前有效靓号。
UserID string `json:"userId"`
}
// gameWhitelistUserResolver 把白名单解析限制在 user-service owner API避免 admin-server 直查用户库复制身份口径。
type gameWhitelistUserResolver interface {
GetUser(context.Context, userclient.GetUserRequest) (*userclient.User, error)
ResolveDisplayUserID(context.Context, userclient.ResolveDisplayUserIDRequest) (*userclient.UserIdentity, error)
}
type gameWhitelistUserDTO struct {
UserID string `json:"userId"`
DisplayUserID string `json:"displayUserId"`
Username string `json:"username"`
Avatar string `json:"avatar"`
Status string `json:"status"`
CreatedByAdminID int64 `json:"createdByAdminId"`
CreatedAtMS int64 `json:"createdAtMs"`
}
// SetGameWhitelistEnabled 只切换访问策略,不隐式增删成员;空白名单开启后应明确表现为无人可见。
func (h *Handler) SetGameWhitelistEnabled(c *gin.Context) {
gameID := strings.TrimSpace(c.Param("game_id"))
var req gameWhitelistEnabledRequest
if gameID == "" || c.ShouldBindJSON(&req) != nil {
response.BadRequest(c, "游戏白名单参数不正确")
return
}
resp, err := h.game.SetGameWhitelistEnabled(c.Request.Context(), &gamev1.SetGameWhitelistEnabledRequest{
Meta: requestMeta(c), GameId: gameID, Enabled: req.Enabled,
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
h.auditLog(c, "set-game-whitelist-enabled", "game_catalog", gameID, strconv.FormatBool(req.Enabled))
response.OK(c, catalogFromProto(resp.GetGame()))
}
// ListGameWhitelistUsers 从 game-service 读取成员事实,再批量补 user-service 展示资料,避免复制用户主数据到游戏库。
func (h *Handler) ListGameWhitelistUsers(c *gin.Context) {
gameID := strings.TrimSpace(c.Param("game_id"))
if gameID == "" {
response.BadRequest(c, "游戏 ID 参数不正确")
return
}
resp, err := h.game.ListGameWhitelistUsers(c.Request.Context(), &gamev1.ListGameWhitelistUsersRequest{
Meta: requestMeta(c), GameId: gameID, PageSize: int32(parsePositiveInt(c.Query("pageSize"), 200)),
})
if err != nil {
response.ServerError(c, "获取游戏白名单失败")
return
}
userIDs := make([]int64, 0, len(resp.GetUsers()))
for _, item := range resp.GetUsers() {
userIDs = append(userIDs, item.GetUserId())
}
profiles := map[int64]*userclient.User{}
if len(userIDs) > 0 {
profiles, err = h.user.BatchGetUsers(c.Request.Context(), userclient.BatchGetUsersRequest{
RequestID: middleware.CurrentRequestID(c), Caller: "admin-server", UserIDs: userIDs,
})
if err != nil {
response.ServerError(c, "获取白名单用户资料失败")
return
}
}
items := make([]gameWhitelistUserDTO, 0, len(resp.GetUsers()))
for _, item := range resp.GetUsers() {
items = append(items, gameWhitelistUserFromProto(item, profiles[item.GetUserId()]))
}
response.OK(c, gin.H{"items": items, "serverTimeMs": resp.GetServerTimeMs()})
}
// AddGameWhitelistUser 先把内部 ID、默认短号或当前有效靓号统一解析为 owner user_id再写游戏访问成员表。
func (h *Handler) AddGameWhitelistUser(c *gin.Context) {
gameID := strings.TrimSpace(c.Param("game_id"))
var req gameWhitelistUserRequest
if gameID == "" || c.ShouldBindJSON(&req) != nil {
response.BadRequest(c, "游戏白名单用户参数不正确")
return
}
keyword := strings.TrimSpace(req.UserID)
if keyword == "" {
response.BadRequest(c, "请输入用户 ID、短号或靓号")
return
}
profile, targetKind, err := resolveGameWhitelistUser(c.Request.Context(), h.user, middleware.CurrentRequestID(c), keyword)
if errors.Is(err, errGameWhitelistUserNotFound) {
response.BadRequest(c, "未找到匹配的用户")
return
}
if err != nil || profile == nil {
response.ServerError(c, "匹配用户失败")
return
}
resp, err := h.game.AddGameWhitelistUser(c.Request.Context(), &gamev1.AddGameWhitelistUserRequest{
Meta: requestMeta(c), GameId: gameID, UserId: profile.UserID,
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
// 审计只记录最终内部 user_id 和解析类型,避免后续短号/靓号变更让历史记录失去指向。
h.auditLog(c, "add-game-whitelist-user", "game_user_whitelist", gameID+":"+strconv.FormatInt(profile.UserID, 10), "added target_kind="+targetKind)
response.OK(c, gameWhitelistUserFromProto(resp.GetUser(), profile))
}
// resolveGameWhitelistUser 延续后台现有身份优先级:短数字和字母靓号优先按展示号解析,雪花长整型优先按内部 ID 读取。
func resolveGameWhitelistUser(ctx context.Context, resolver gameWhitelistUserResolver, requestID string, keyword string) (*userclient.User, string, error) {
keyword = strings.TrimSpace(keyword)
if resolver == nil || keyword == "" {
return nil, "", errGameWhitelistUserNotFound
}
numericID, numericErr := strconv.ParseInt(keyword, 10, 64)
numericOK := numericErr == nil && numericID > 0
userIDChecked := false
// 生产内部 user_id 是雪花长整型;先查长数字可保持已发布内部 ID 输入语义,同时仍允许长数字靓号在未命中后回退解析。
if numericOK && len(strings.TrimLeft(keyword, "0")) >= gameWhitelistInternalUserIDMinDigits {
userIDChecked = true
if profile, found, err := getGameWhitelistUser(ctx, resolver, requestID, numericID); err != nil {
return nil, "", err
} else if found {
return profile, "user_id", nil
}
}
identity, err := resolver.ResolveDisplayUserID(ctx, userclient.ResolveDisplayUserIDRequest{
RequestID: requestID, Caller: "admin-server", DisplayUserID: keyword,
})
if err == nil && identity != nil && identity.UserID > 0 {
profile, found, getErr := getGameWhitelistUser(ctx, resolver, requestID, identity.UserID)
if getErr != nil {
return nil, "", getErr
}
if found {
return profile, "display_user_id", nil
}
} else if err != nil && status.Code(err) != codes.NotFound && status.Code(err) != codes.InvalidArgument {
// user-service 不可用等系统错误不能伪装成用户不存在,否则运营重试时无法判断真实故障。
return nil, "", err
}
if numericOK && !userIDChecked {
if profile, found, err := getGameWhitelistUser(ctx, resolver, requestID, numericID); err != nil {
return nil, "", err
} else if found {
return profile, "user_id", nil
}
}
return nil, "", errGameWhitelistUserNotFound
}
func getGameWhitelistUser(ctx context.Context, resolver gameWhitelistUserResolver, requestID string, userID int64) (*userclient.User, bool, error) {
profile, err := resolver.GetUser(ctx, userclient.GetUserRequest{RequestID: requestID, Caller: "admin-server", UserID: userID})
if status.Code(err) == codes.NotFound || profile == nil && err == nil {
return nil, false, nil
}
if err != nil {
return nil, false, err
}
return profile, true, nil
}
func (h *Handler) DeleteGameWhitelistUser(c *gin.Context) {
gameID := strings.TrimSpace(c.Param("game_id"))
userID, err := strconv.ParseInt(strings.TrimSpace(c.Param("user_id")), 10, 64)
if gameID == "" || err != nil || userID <= 0 {
response.BadRequest(c, "游戏白名单用户参数不正确")
return
}
if _, err := h.game.DeleteGameWhitelistUser(c.Request.Context(), &gamev1.DeleteGameWhitelistUserRequest{
Meta: requestMeta(c), GameId: gameID, UserId: userID,
}); err != nil {
response.BadRequest(c, err.Error())
return
}
h.auditLog(c, "delete-game-whitelist-user", "game_user_whitelist", gameID+":"+strconv.FormatInt(userID, 10), "deleted")
response.OK(c, gin.H{"userId": strconv.FormatInt(userID, 10)})
}
func gameWhitelistUserFromProto(item *gamev1.GameWhitelistUser, profile *userclient.User) gameWhitelistUserDTO {
if item == nil {
return gameWhitelistUserDTO{}
}
dto := gameWhitelistUserDTO{
UserID: strconv.FormatInt(item.GetUserId(), 10), CreatedByAdminID: item.GetCreatedByAdminId(), CreatedAtMS: item.GetCreatedAtMs(),
}
if profile != nil {
dto.DisplayUserID = profile.DisplayUserID
dto.Username = profile.Username
dto.Avatar = profile.Avatar
dto.Status = profile.Status
}
return dto
}