222 lines
8.6 KiB
Go
222 lines
8.6 KiB
Go
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
|
||
}
|