增加游戏用户白名单
This commit is contained in:
parent
24ebf737ae
commit
aac2b4dc76
File diff suppressed because it is too large
Load Diff
@ -49,6 +49,8 @@ message GameCatalogItem {
|
|||||||
int64 created_at_ms = 15;
|
int64 created_at_ms = 15;
|
||||||
int64 updated_at_ms = 16;
|
int64 updated_at_ms = 16;
|
||||||
int32 safe_height = 17;
|
int32 safe_height = 17;
|
||||||
|
// whitelist_enabled 关闭时保持历史全量可见语义;开启后只向 game_user_whitelist 中的用户展示并允许启动。
|
||||||
|
bool whitelist_enabled = 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AppGame {
|
message AppGame {
|
||||||
@ -682,6 +684,53 @@ message DiceRobotResponse {
|
|||||||
int64 server_time_ms = 2;
|
int64 server_time_ms = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GameWhitelistUser 是 game-service 持有的游戏访问成员事实;用户展示资料由 admin-server 向 user-service 补齐。
|
||||||
|
message GameWhitelistUser {
|
||||||
|
string app_code = 1;
|
||||||
|
string game_id = 2;
|
||||||
|
int64 user_id = 3;
|
||||||
|
int64 created_by_admin_id = 4;
|
||||||
|
int64 created_at_ms = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetGameWhitelistEnabledRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string game_id = 2;
|
||||||
|
bool enabled = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListGameWhitelistUsersRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string game_id = 2;
|
||||||
|
int32 page_size = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListGameWhitelistUsersResponse {
|
||||||
|
repeated GameWhitelistUser users = 1;
|
||||||
|
int64 server_time_ms = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddGameWhitelistUserRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string game_id = 2;
|
||||||
|
int64 user_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GameWhitelistUserResponse {
|
||||||
|
GameWhitelistUser user = 1;
|
||||||
|
int64 server_time_ms = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteGameWhitelistUserRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string game_id = 2;
|
||||||
|
int64 user_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteGameWhitelistUserResponse {
|
||||||
|
int64 server_time_ms = 1;
|
||||||
|
}
|
||||||
|
|
||||||
service GameAppService {
|
service GameAppService {
|
||||||
rpc ListGames(ListGamesRequest) returns (ListGamesResponse);
|
rpc ListGames(ListGamesRequest) returns (ListGamesResponse);
|
||||||
rpc ListRecentGames(ListRecentGamesRequest) returns (ListGamesResponse);
|
rpc ListRecentGames(ListRecentGamesRequest) returns (ListGamesResponse);
|
||||||
@ -718,6 +767,10 @@ service GameAdminService {
|
|||||||
rpc UpsertCatalog(UpsertCatalogRequest) returns (CatalogResponse);
|
rpc UpsertCatalog(UpsertCatalogRequest) returns (CatalogResponse);
|
||||||
rpc SetGameStatus(SetGameStatusRequest) returns (CatalogResponse);
|
rpc SetGameStatus(SetGameStatusRequest) returns (CatalogResponse);
|
||||||
rpc DeleteCatalog(DeleteCatalogRequest) returns (DeleteCatalogResponse);
|
rpc DeleteCatalog(DeleteCatalogRequest) returns (DeleteCatalogResponse);
|
||||||
|
rpc SetGameWhitelistEnabled(SetGameWhitelistEnabledRequest) returns (CatalogResponse);
|
||||||
|
rpc ListGameWhitelistUsers(ListGameWhitelistUsersRequest) returns (ListGameWhitelistUsersResponse);
|
||||||
|
rpc AddGameWhitelistUser(AddGameWhitelistUserRequest) returns (GameWhitelistUserResponse);
|
||||||
|
rpc DeleteGameWhitelistUser(DeleteGameWhitelistUserRequest) returns (DeleteGameWhitelistUserResponse);
|
||||||
rpc ListSelfGames(ListSelfGamesRequest) returns (ListSelfGamesResponse);
|
rpc ListSelfGames(ListSelfGamesRequest) returns (ListSelfGamesResponse);
|
||||||
rpc UpdateDiceConfig(UpdateDiceConfigRequest) returns (DiceConfigResponse);
|
rpc UpdateDiceConfig(UpdateDiceConfigRequest) returns (DiceConfigResponse);
|
||||||
rpc GetSelfGameNewUserPolicy(GetSelfGameNewUserPolicyRequest) returns (SelfGameNewUserPolicyResponse);
|
rpc GetSelfGameNewUserPolicy(GetSelfGameNewUserPolicyRequest) returns (SelfGameNewUserPolicyResponse);
|
||||||
|
|||||||
@ -943,6 +943,10 @@ const (
|
|||||||
GameAdminService_UpsertCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertCatalog"
|
GameAdminService_UpsertCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertCatalog"
|
||||||
GameAdminService_SetGameStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetGameStatus"
|
GameAdminService_SetGameStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetGameStatus"
|
||||||
GameAdminService_DeleteCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteCatalog"
|
GameAdminService_DeleteCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteCatalog"
|
||||||
|
GameAdminService_SetGameWhitelistEnabled_FullMethodName = "/hyapp.game.v1.GameAdminService/SetGameWhitelistEnabled"
|
||||||
|
GameAdminService_ListGameWhitelistUsers_FullMethodName = "/hyapp.game.v1.GameAdminService/ListGameWhitelistUsers"
|
||||||
|
GameAdminService_AddGameWhitelistUser_FullMethodName = "/hyapp.game.v1.GameAdminService/AddGameWhitelistUser"
|
||||||
|
GameAdminService_DeleteGameWhitelistUser_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteGameWhitelistUser"
|
||||||
GameAdminService_ListSelfGames_FullMethodName = "/hyapp.game.v1.GameAdminService/ListSelfGames"
|
GameAdminService_ListSelfGames_FullMethodName = "/hyapp.game.v1.GameAdminService/ListSelfGames"
|
||||||
GameAdminService_UpdateDiceConfig_FullMethodName = "/hyapp.game.v1.GameAdminService/UpdateDiceConfig"
|
GameAdminService_UpdateDiceConfig_FullMethodName = "/hyapp.game.v1.GameAdminService/UpdateDiceConfig"
|
||||||
GameAdminService_GetSelfGameNewUserPolicy_FullMethodName = "/hyapp.game.v1.GameAdminService/GetSelfGameNewUserPolicy"
|
GameAdminService_GetSelfGameNewUserPolicy_FullMethodName = "/hyapp.game.v1.GameAdminService/GetSelfGameNewUserPolicy"
|
||||||
@ -972,6 +976,10 @@ type GameAdminServiceClient interface {
|
|||||||
UpsertCatalog(ctx context.Context, in *UpsertCatalogRequest, opts ...grpc.CallOption) (*CatalogResponse, error)
|
UpsertCatalog(ctx context.Context, in *UpsertCatalogRequest, opts ...grpc.CallOption) (*CatalogResponse, error)
|
||||||
SetGameStatus(ctx context.Context, in *SetGameStatusRequest, opts ...grpc.CallOption) (*CatalogResponse, error)
|
SetGameStatus(ctx context.Context, in *SetGameStatusRequest, opts ...grpc.CallOption) (*CatalogResponse, error)
|
||||||
DeleteCatalog(ctx context.Context, in *DeleteCatalogRequest, opts ...grpc.CallOption) (*DeleteCatalogResponse, error)
|
DeleteCatalog(ctx context.Context, in *DeleteCatalogRequest, opts ...grpc.CallOption) (*DeleteCatalogResponse, error)
|
||||||
|
SetGameWhitelistEnabled(ctx context.Context, in *SetGameWhitelistEnabledRequest, opts ...grpc.CallOption) (*CatalogResponse, error)
|
||||||
|
ListGameWhitelistUsers(ctx context.Context, in *ListGameWhitelistUsersRequest, opts ...grpc.CallOption) (*ListGameWhitelistUsersResponse, error)
|
||||||
|
AddGameWhitelistUser(ctx context.Context, in *AddGameWhitelistUserRequest, opts ...grpc.CallOption) (*GameWhitelistUserResponse, error)
|
||||||
|
DeleteGameWhitelistUser(ctx context.Context, in *DeleteGameWhitelistUserRequest, opts ...grpc.CallOption) (*DeleteGameWhitelistUserResponse, error)
|
||||||
ListSelfGames(ctx context.Context, in *ListSelfGamesRequest, opts ...grpc.CallOption) (*ListSelfGamesResponse, error)
|
ListSelfGames(ctx context.Context, in *ListSelfGamesRequest, opts ...grpc.CallOption) (*ListSelfGamesResponse, error)
|
||||||
UpdateDiceConfig(ctx context.Context, in *UpdateDiceConfigRequest, opts ...grpc.CallOption) (*DiceConfigResponse, error)
|
UpdateDiceConfig(ctx context.Context, in *UpdateDiceConfigRequest, opts ...grpc.CallOption) (*DiceConfigResponse, error)
|
||||||
GetSelfGameNewUserPolicy(ctx context.Context, in *GetSelfGameNewUserPolicyRequest, opts ...grpc.CallOption) (*SelfGameNewUserPolicyResponse, error)
|
GetSelfGameNewUserPolicy(ctx context.Context, in *GetSelfGameNewUserPolicyRequest, opts ...grpc.CallOption) (*SelfGameNewUserPolicyResponse, error)
|
||||||
@ -1059,6 +1067,46 @@ func (c *gameAdminServiceClient) DeleteCatalog(ctx context.Context, in *DeleteCa
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *gameAdminServiceClient) SetGameWhitelistEnabled(ctx context.Context, in *SetGameWhitelistEnabledRequest, opts ...grpc.CallOption) (*CatalogResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(CatalogResponse)
|
||||||
|
err := c.cc.Invoke(ctx, GameAdminService_SetGameWhitelistEnabled_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *gameAdminServiceClient) ListGameWhitelistUsers(ctx context.Context, in *ListGameWhitelistUsersRequest, opts ...grpc.CallOption) (*ListGameWhitelistUsersResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListGameWhitelistUsersResponse)
|
||||||
|
err := c.cc.Invoke(ctx, GameAdminService_ListGameWhitelistUsers_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *gameAdminServiceClient) AddGameWhitelistUser(ctx context.Context, in *AddGameWhitelistUserRequest, opts ...grpc.CallOption) (*GameWhitelistUserResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(GameWhitelistUserResponse)
|
||||||
|
err := c.cc.Invoke(ctx, GameAdminService_AddGameWhitelistUser_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *gameAdminServiceClient) DeleteGameWhitelistUser(ctx context.Context, in *DeleteGameWhitelistUserRequest, opts ...grpc.CallOption) (*DeleteGameWhitelistUserResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(DeleteGameWhitelistUserResponse)
|
||||||
|
err := c.cc.Invoke(ctx, GameAdminService_DeleteGameWhitelistUser_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *gameAdminServiceClient) ListSelfGames(ctx context.Context, in *ListSelfGamesRequest, opts ...grpc.CallOption) (*ListSelfGamesResponse, error) {
|
func (c *gameAdminServiceClient) ListSelfGames(ctx context.Context, in *ListSelfGamesRequest, opts ...grpc.CallOption) (*ListSelfGamesResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(ListSelfGamesResponse)
|
out := new(ListSelfGamesResponse)
|
||||||
@ -1239,6 +1287,10 @@ type GameAdminServiceServer interface {
|
|||||||
UpsertCatalog(context.Context, *UpsertCatalogRequest) (*CatalogResponse, error)
|
UpsertCatalog(context.Context, *UpsertCatalogRequest) (*CatalogResponse, error)
|
||||||
SetGameStatus(context.Context, *SetGameStatusRequest) (*CatalogResponse, error)
|
SetGameStatus(context.Context, *SetGameStatusRequest) (*CatalogResponse, error)
|
||||||
DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error)
|
DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error)
|
||||||
|
SetGameWhitelistEnabled(context.Context, *SetGameWhitelistEnabledRequest) (*CatalogResponse, error)
|
||||||
|
ListGameWhitelistUsers(context.Context, *ListGameWhitelistUsersRequest) (*ListGameWhitelistUsersResponse, error)
|
||||||
|
AddGameWhitelistUser(context.Context, *AddGameWhitelistUserRequest) (*GameWhitelistUserResponse, error)
|
||||||
|
DeleteGameWhitelistUser(context.Context, *DeleteGameWhitelistUserRequest) (*DeleteGameWhitelistUserResponse, error)
|
||||||
ListSelfGames(context.Context, *ListSelfGamesRequest) (*ListSelfGamesResponse, error)
|
ListSelfGames(context.Context, *ListSelfGamesRequest) (*ListSelfGamesResponse, error)
|
||||||
UpdateDiceConfig(context.Context, *UpdateDiceConfigRequest) (*DiceConfigResponse, error)
|
UpdateDiceConfig(context.Context, *UpdateDiceConfigRequest) (*DiceConfigResponse, error)
|
||||||
GetSelfGameNewUserPolicy(context.Context, *GetSelfGameNewUserPolicyRequest) (*SelfGameNewUserPolicyResponse, error)
|
GetSelfGameNewUserPolicy(context.Context, *GetSelfGameNewUserPolicyRequest) (*SelfGameNewUserPolicyResponse, error)
|
||||||
@ -1284,6 +1336,18 @@ func (UnimplementedGameAdminServiceServer) SetGameStatus(context.Context, *SetGa
|
|||||||
func (UnimplementedGameAdminServiceServer) DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error) {
|
func (UnimplementedGameAdminServiceServer) DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteCatalog not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method DeleteCatalog not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedGameAdminServiceServer) SetGameWhitelistEnabled(context.Context, *SetGameWhitelistEnabledRequest) (*CatalogResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method SetGameWhitelistEnabled not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedGameAdminServiceServer) ListGameWhitelistUsers(context.Context, *ListGameWhitelistUsersRequest) (*ListGameWhitelistUsersResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListGameWhitelistUsers not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedGameAdminServiceServer) AddGameWhitelistUser(context.Context, *AddGameWhitelistUserRequest) (*GameWhitelistUserResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method AddGameWhitelistUser not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedGameAdminServiceServer) DeleteGameWhitelistUser(context.Context, *DeleteGameWhitelistUserRequest) (*DeleteGameWhitelistUserResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method DeleteGameWhitelistUser not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedGameAdminServiceServer) ListSelfGames(context.Context, *ListSelfGamesRequest) (*ListSelfGamesResponse, error) {
|
func (UnimplementedGameAdminServiceServer) ListSelfGames(context.Context, *ListSelfGamesRequest) (*ListSelfGamesResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListSelfGames not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListSelfGames not implemented")
|
||||||
}
|
}
|
||||||
@ -1464,6 +1528,78 @@ func _GameAdminService_DeleteCatalog_Handler(srv interface{}, ctx context.Contex
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _GameAdminService_SetGameWhitelistEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SetGameWhitelistEnabledRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(GameAdminServiceServer).SetGameWhitelistEnabled(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: GameAdminService_SetGameWhitelistEnabled_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(GameAdminServiceServer).SetGameWhitelistEnabled(ctx, req.(*SetGameWhitelistEnabledRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _GameAdminService_ListGameWhitelistUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListGameWhitelistUsersRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(GameAdminServiceServer).ListGameWhitelistUsers(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: GameAdminService_ListGameWhitelistUsers_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(GameAdminServiceServer).ListGameWhitelistUsers(ctx, req.(*ListGameWhitelistUsersRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _GameAdminService_AddGameWhitelistUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(AddGameWhitelistUserRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(GameAdminServiceServer).AddGameWhitelistUser(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: GameAdminService_AddGameWhitelistUser_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(GameAdminServiceServer).AddGameWhitelistUser(ctx, req.(*AddGameWhitelistUserRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _GameAdminService_DeleteGameWhitelistUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DeleteGameWhitelistUserRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(GameAdminServiceServer).DeleteGameWhitelistUser(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: GameAdminService_DeleteGameWhitelistUser_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(GameAdminServiceServer).DeleteGameWhitelistUser(ctx, req.(*DeleteGameWhitelistUserRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _GameAdminService_ListSelfGames_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _GameAdminService_ListSelfGames_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(ListSelfGamesRequest)
|
in := new(ListSelfGamesRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -1801,6 +1937,22 @@ var GameAdminService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "DeleteCatalog",
|
MethodName: "DeleteCatalog",
|
||||||
Handler: _GameAdminService_DeleteCatalog_Handler,
|
Handler: _GameAdminService_DeleteCatalog_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SetGameWhitelistEnabled",
|
||||||
|
Handler: _GameAdminService_SetGameWhitelistEnabled_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListGameWhitelistUsers",
|
||||||
|
Handler: _GameAdminService_ListGameWhitelistUsers_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "AddGameWhitelistUser",
|
||||||
|
Handler: _GameAdminService_AddGameWhitelistUser_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "DeleteGameWhitelistUser",
|
||||||
|
Handler: _GameAdminService_DeleteGameWhitelistUser_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "ListSelfGames",
|
MethodName: "ListSelfGames",
|
||||||
Handler: _GameAdminService_ListSelfGames_Handler,
|
Handler: _GameAdminService_ListSelfGames_Handler,
|
||||||
|
|||||||
@ -16,6 +16,10 @@ type Client interface {
|
|||||||
UpsertCatalog(ctx context.Context, req *gamev1.UpsertCatalogRequest) (*gamev1.CatalogResponse, error)
|
UpsertCatalog(ctx context.Context, req *gamev1.UpsertCatalogRequest) (*gamev1.CatalogResponse, error)
|
||||||
SetGameStatus(ctx context.Context, req *gamev1.SetGameStatusRequest) (*gamev1.CatalogResponse, error)
|
SetGameStatus(ctx context.Context, req *gamev1.SetGameStatusRequest) (*gamev1.CatalogResponse, error)
|
||||||
DeleteCatalog(ctx context.Context, req *gamev1.DeleteCatalogRequest) (*gamev1.DeleteCatalogResponse, error)
|
DeleteCatalog(ctx context.Context, req *gamev1.DeleteCatalogRequest) (*gamev1.DeleteCatalogResponse, error)
|
||||||
|
SetGameWhitelistEnabled(ctx context.Context, req *gamev1.SetGameWhitelistEnabledRequest) (*gamev1.CatalogResponse, error)
|
||||||
|
ListGameWhitelistUsers(ctx context.Context, req *gamev1.ListGameWhitelistUsersRequest) (*gamev1.ListGameWhitelistUsersResponse, error)
|
||||||
|
AddGameWhitelistUser(ctx context.Context, req *gamev1.AddGameWhitelistUserRequest) (*gamev1.GameWhitelistUserResponse, error)
|
||||||
|
DeleteGameWhitelistUser(ctx context.Context, req *gamev1.DeleteGameWhitelistUserRequest) (*gamev1.DeleteGameWhitelistUserResponse, error)
|
||||||
ListSelfGames(ctx context.Context, req *gamev1.ListSelfGamesRequest) (*gamev1.ListSelfGamesResponse, error)
|
ListSelfGames(ctx context.Context, req *gamev1.ListSelfGamesRequest) (*gamev1.ListSelfGamesResponse, error)
|
||||||
UpdateDiceConfig(ctx context.Context, req *gamev1.UpdateDiceConfigRequest) (*gamev1.DiceConfigResponse, error)
|
UpdateDiceConfig(ctx context.Context, req *gamev1.UpdateDiceConfigRequest) (*gamev1.DiceConfigResponse, error)
|
||||||
GetSelfGameNewUserPolicy(ctx context.Context, req *gamev1.GetSelfGameNewUserPolicyRequest) (*gamev1.SelfGameNewUserPolicyResponse, error)
|
GetSelfGameNewUserPolicy(ctx context.Context, req *gamev1.GetSelfGameNewUserPolicyRequest) (*gamev1.SelfGameNewUserPolicyResponse, error)
|
||||||
@ -67,6 +71,22 @@ func (c *GRPCClient) DeleteCatalog(ctx context.Context, req *gamev1.DeleteCatalo
|
|||||||
return c.client.DeleteCatalog(ctx, req)
|
return c.client.DeleteCatalog(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) SetGameWhitelistEnabled(ctx context.Context, req *gamev1.SetGameWhitelistEnabledRequest) (*gamev1.CatalogResponse, error) {
|
||||||
|
return c.client.SetGameWhitelistEnabled(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) ListGameWhitelistUsers(ctx context.Context, req *gamev1.ListGameWhitelistUsersRequest) (*gamev1.ListGameWhitelistUsersResponse, error) {
|
||||||
|
return c.client.ListGameWhitelistUsers(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) AddGameWhitelistUser(ctx context.Context, req *gamev1.AddGameWhitelistUserRequest) (*gamev1.GameWhitelistUserResponse, error) {
|
||||||
|
return c.client.AddGameWhitelistUser(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) DeleteGameWhitelistUser(ctx context.Context, req *gamev1.DeleteGameWhitelistUserRequest) (*gamev1.DeleteGameWhitelistUserResponse, error) {
|
||||||
|
return c.client.DeleteGameWhitelistUser(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *GRPCClient) ListSelfGames(ctx context.Context, req *gamev1.ListSelfGamesRequest) (*gamev1.ListSelfGamesResponse, error) {
|
func (c *GRPCClient) ListSelfGames(ctx context.Context, req *gamev1.ListSelfGamesRequest) (*gamev1.ListSelfGamesResponse, error) {
|
||||||
return c.client.ListSelfGames(ctx, req)
|
return c.client.ListSelfGames(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,6 +46,8 @@ type catalogDTO struct {
|
|||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
CreatedAtMS int64 `json:"createdAtMs"`
|
CreatedAtMS int64 `json:"createdAtMs"`
|
||||||
UpdatedAtMS int64 `json:"updatedAtMs"`
|
UpdatedAtMS int64 `json:"updatedAtMs"`
|
||||||
|
// whitelistEnabled 只表示访问策略是否生效;成员通过独立接口按需加载,避免目录列表膨胀。
|
||||||
|
WhitelistEnabled bool `json:"whitelistEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type diceConfigDTO struct {
|
type diceConfigDTO struct {
|
||||||
@ -215,23 +217,24 @@ func catalogFromProto(item *gamev1.GameCatalogItem) catalogDTO {
|
|||||||
return catalogDTO{}
|
return catalogDTO{}
|
||||||
}
|
}
|
||||||
return catalogDTO{
|
return catalogDTO{
|
||||||
AppCode: item.GetAppCode(),
|
AppCode: item.GetAppCode(),
|
||||||
GameID: item.GetGameId(),
|
GameID: item.GetGameId(),
|
||||||
PlatformCode: item.GetPlatformCode(),
|
PlatformCode: item.GetPlatformCode(),
|
||||||
ProviderGameID: item.GetProviderGameId(),
|
ProviderGameID: item.GetProviderGameId(),
|
||||||
GameName: item.GetGameName(),
|
GameName: item.GetGameName(),
|
||||||
Category: item.GetCategory(),
|
Category: item.GetCategory(),
|
||||||
IconURL: item.GetIconUrl(),
|
IconURL: item.GetIconUrl(),
|
||||||
CoverURL: item.GetCoverUrl(),
|
CoverURL: item.GetCoverUrl(),
|
||||||
LaunchMode: item.GetLaunchMode(),
|
LaunchMode: item.GetLaunchMode(),
|
||||||
Orientation: item.GetOrientation(),
|
Orientation: item.GetOrientation(),
|
||||||
SafeHeight: item.GetSafeHeight(),
|
SafeHeight: item.GetSafeHeight(),
|
||||||
MinCoin: item.GetMinCoin(),
|
MinCoin: item.GetMinCoin(),
|
||||||
Status: item.GetStatus(),
|
Status: item.GetStatus(),
|
||||||
SortOrder: item.GetSortOrder(),
|
SortOrder: item.GetSortOrder(),
|
||||||
Tags: item.GetTags(),
|
Tags: item.GetTags(),
|
||||||
CreatedAtMS: item.GetCreatedAtMs(),
|
CreatedAtMS: item.GetCreatedAtMs(),
|
||||||
UpdatedAtMS: item.GetUpdatedAtMs(),
|
UpdatedAtMS: item.GetUpdatedAtMs(),
|
||||||
|
WhitelistEnabled: item.GetWhitelistEnabled(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
150
server/admin/internal/modules/gamemanagement/game_whitelist.go
Normal file
150
server/admin/internal/modules/gamemanagement/game_whitelist.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package gamemanagement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gameWhitelistEnabledRequest struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type gameWhitelistUserRequest struct {
|
||||||
|
// 字符串承载 user_id,避免浏览器 Number 对大整数发生精度损失。
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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 先按内部 user_id 读取 owner 主数据,只有真实存在的用户才写入游戏访问成员表。
|
||||||
|
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
|
||||||
|
}
|
||||||
|
userID, err := strconv.ParseInt(strings.TrimSpace(req.UserID), 10, 64)
|
||||||
|
if err != nil || userID <= 0 {
|
||||||
|
response.BadRequest(c, "用户 ID 参数不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
profile, err := h.user.GetUser(c.Request.Context(), userclient.GetUserRequest{
|
||||||
|
RequestID: middleware.CurrentRequestID(c), Caller: "admin-server", UserID: userID,
|
||||||
|
})
|
||||||
|
if err != nil || profile == nil {
|
||||||
|
response.BadRequest(c, "用户不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.game.AddGameWhitelistUser(c.Request.Context(), &gamev1.AddGameWhitelistUserRequest{
|
||||||
|
Meta: requestMeta(c), GameId: gameID, UserId: userID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.auditLog(c, "add-game-whitelist-user", "game_user_whitelist", gameID+":"+req.UserID, "added")
|
||||||
|
response.OK(c, gameWhitelistUserFromProto(resp.GetUser(), profile))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@ -20,6 +20,10 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
|
|||||||
protected.PATCH("/admin/game/games/:game_id", middleware.RequirePermission("game:update"), h.UpdateCatalog)
|
protected.PATCH("/admin/game/games/:game_id", middleware.RequirePermission("game:update"), h.UpdateCatalog)
|
||||||
protected.PATCH("/admin/game/games/:game_id/status", middleware.RequirePermission("game:status"), h.SetGameStatus)
|
protected.PATCH("/admin/game/games/:game_id/status", middleware.RequirePermission("game:status"), h.SetGameStatus)
|
||||||
protected.DELETE("/admin/game/games/:game_id", middleware.RequireAnyPermission("game:delete", "game:update"), h.DeleteCatalog)
|
protected.DELETE("/admin/game/games/:game_id", middleware.RequireAnyPermission("game:delete", "game:update"), h.DeleteCatalog)
|
||||||
|
protected.PATCH("/admin/game/games/:game_id/whitelist", middleware.RequirePermission("game:update"), h.SetGameWhitelistEnabled)
|
||||||
|
protected.GET("/admin/game/games/:game_id/whitelist/users", middleware.RequirePermission("game:view"), h.ListGameWhitelistUsers)
|
||||||
|
protected.POST("/admin/game/games/:game_id/whitelist/users", middleware.RequirePermission("game:update"), h.AddGameWhitelistUser)
|
||||||
|
protected.DELETE("/admin/game/games/:game_id/whitelist/users/:user_id", middleware.RequirePermission("game:update"), h.DeleteGameWhitelistUser)
|
||||||
protected.GET("/admin/game/self-games", middleware.RequirePermission("game:view"), h.ListSelfGames)
|
protected.GET("/admin/game/self-games", middleware.RequirePermission("game:view"), h.ListSelfGames)
|
||||||
protected.PATCH("/admin/game/self-games/:game_id/config", middleware.RequirePermission("game:update"), h.UpdateDiceConfig)
|
protected.PATCH("/admin/game/self-games/:game_id/config", middleware.RequirePermission("game:update"), h.UpdateDiceConfig)
|
||||||
protected.GET("/admin/game/self-games/:game_id/new-user-policy", middleware.RequirePermission("game:view"), h.GetSelfGameNewUserPolicy)
|
protected.GET("/admin/game/self-games/:game_id/new-user-policy", middleware.RequirePermission("game:view"), h.GetSelfGameNewUserPolicy)
|
||||||
|
|||||||
@ -36,6 +36,7 @@ CREATE TABLE IF NOT EXISTS game_catalog (
|
|||||||
safe_height INT NOT NULL DEFAULT 0 COMMENT '游戏安全高',
|
safe_height INT NOT NULL DEFAULT 0 COMMENT '游戏安全高',
|
||||||
min_coin BIGINT NOT NULL DEFAULT 0 COMMENT '最小金币',
|
min_coin BIGINT NOT NULL DEFAULT 0 COMMENT '最小金币',
|
||||||
status VARCHAR(32) NOT NULL COMMENT '业务状态',
|
status VARCHAR(32) NOT NULL COMMENT '业务状态',
|
||||||
|
whitelist_enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否仅对白名单用户展示并允许启动',
|
||||||
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重,数值越小越靠前',
|
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重,数值越小越靠前',
|
||||||
tags JSON NULL COMMENT '标签',
|
tags JSON NULL COMMENT '标签',
|
||||||
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
||||||
@ -45,6 +46,27 @@ CREATE TABLE IF NOT EXISTS game_catalog (
|
|||||||
KEY idx_game_platform_status(app_code, platform_code, status, sort_order)
|
KEY idx_game_platform_status(app_code, platform_code, status, sort_order)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏目录表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏目录表';
|
||||||
|
|
||||||
|
-- 白名单关闭时不读取本表;开启时所有在线校验均命中复合主键,后台列表使用时间覆盖索引做有限读取。
|
||||||
|
CREATE TABLE IF NOT EXISTS game_user_whitelist (
|
||||||
|
app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离',
|
||||||
|
game_id VARCHAR(96) NOT NULL COMMENT '游戏 ID',
|
||||||
|
user_id BIGINT NOT NULL COMMENT '允许访问的内部用户 ID',
|
||||||
|
created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '添加成员的管理员 ID',
|
||||||
|
created_at_ms BIGINT NOT NULL COMMENT '添加时间,UTC epoch ms',
|
||||||
|
PRIMARY KEY(app_code, game_id, user_id),
|
||||||
|
KEY idx_game_whitelist_game_time(app_code, game_id, created_at_ms, user_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏用户白名单';
|
||||||
|
|
||||||
|
-- 已有库的 game_catalog 不会因 CREATE TABLE IF NOT EXISTS 自动补列;目录行很少,幂等 DDL 的锁影响可控且只执行一次。
|
||||||
|
SET @ddl := IF(
|
||||||
|
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_catalog' AND COLUMN_NAME = 'whitelist_enabled') = 0,
|
||||||
|
'ALTER TABLE game_catalog ADD COLUMN whitelist_enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否仅对白名单用户展示并允许启动'' AFTER status',
|
||||||
|
'SELECT 1'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS game_display_rules (
|
CREATE TABLE IF NOT EXISTS game_display_rules (
|
||||||
app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离',
|
app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离',
|
||||||
rule_id VARCHAR(96) NOT NULL COMMENT '规则 ID',
|
rule_id VARCHAR(96) NOT NULL COMMENT '规则 ID',
|
||||||
|
|||||||
@ -78,6 +78,17 @@ type CatalogItem struct {
|
|||||||
Tags []string
|
Tags []string
|
||||||
CreatedAtMS int64
|
CreatedAtMS int64
|
||||||
UpdatedAtMS int64
|
UpdatedAtMS int64
|
||||||
|
// WhitelistEnabled 默认 false,保证迁移后旧游戏仍面向原有用户集合开放。
|
||||||
|
WhitelistEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhitelistUser 保存目录访问成员和后台操作来源;用户昵称、头像等主数据仍由 user-service 持有。
|
||||||
|
type WhitelistUser struct {
|
||||||
|
AppCode string
|
||||||
|
GameID string
|
||||||
|
UserID int64
|
||||||
|
CreatedByAdminID int64
|
||||||
|
CreatedAtMS int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppGame 是 App 语音房内看到的跨平台合并游戏卡片。
|
// AppGame 是 App 语音房内看到的跨平台合并游戏卡片。
|
||||||
|
|||||||
@ -57,6 +57,11 @@ type Repository interface {
|
|||||||
UpsertCatalog(ctx context.Context, item gamedomain.CatalogItem) (gamedomain.CatalogItem, error)
|
UpsertCatalog(ctx context.Context, item gamedomain.CatalogItem) (gamedomain.CatalogItem, error)
|
||||||
SetGameStatus(ctx context.Context, appCode string, gameID string, status string, nowMs int64) (gamedomain.CatalogItem, error)
|
SetGameStatus(ctx context.Context, appCode string, gameID string, status string, nowMs int64) (gamedomain.CatalogItem, error)
|
||||||
DeleteCatalog(ctx context.Context, appCode string, gameID string) error
|
DeleteCatalog(ctx context.Context, appCode string, gameID string) error
|
||||||
|
CanUserAccessGame(ctx context.Context, appCode string, gameID string, userID int64) (bool, error)
|
||||||
|
SetGameWhitelistEnabled(ctx context.Context, appCode string, gameID string, enabled bool, nowMS int64) (gamedomain.CatalogItem, error)
|
||||||
|
ListGameWhitelistUsers(ctx context.Context, appCode string, gameID string, pageSize int32) ([]gamedomain.WhitelistUser, error)
|
||||||
|
AddGameWhitelistUser(ctx context.Context, item gamedomain.WhitelistUser) (gamedomain.WhitelistUser, error)
|
||||||
|
DeleteGameWhitelistUser(ctx context.Context, appCode string, gameID string, userID int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// WalletClient 是 game-service 调 wallet-service 的最小改账依赖。
|
// WalletClient 是 game-service 调 wallet-service 的最小改账依赖。
|
||||||
@ -276,6 +281,14 @@ func (s *Service) LaunchGame(ctx context.Context, command LaunchCommand) (Launch
|
|||||||
if game.PlatformStatus != gamedomain.StatusActive || game.Status != gamedomain.StatusActive {
|
if game.PlatformStatus != gamedomain.StatusActive || game.Status != gamedomain.StatusActive {
|
||||||
return LaunchResult{}, xerr.New(xerr.Conflict, "game is not launchable")
|
return LaunchResult{}, xerr.New(xerr.Conflict, "game is not launchable")
|
||||||
}
|
}
|
||||||
|
// 列表过滤只改善客户端体验,不能作为访问控制;启动链路必须再次按真实登录 user_id 校验,阻止猜 game_id 绕过白名单。
|
||||||
|
allowed, err := s.repository.CanUserAccessGame(ctx, command.AppCode, game.GameID, command.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return LaunchResult{}, err
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
return LaunchResult{}, xerr.New(xerr.NotFound, "game not found")
|
||||||
|
}
|
||||||
regionID, countryID := command.RegionID, command.CountryID
|
regionID, countryID := command.RegionID, command.CountryID
|
||||||
if regionID <= 0 || countryID <= 0 {
|
if regionID <= 0 || countryID <= 0 {
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@ -69,6 +69,26 @@ func TestLaunchGameCreatesSessionAndRejectsMaintenance(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLaunchGameRejectsUserOutsideEnabledWhitelist(t *testing.T) {
|
||||||
|
allowed := false
|
||||||
|
repo := &fakeRepository{
|
||||||
|
accessAllowed: &allowed,
|
||||||
|
launchable: gamedomain.LaunchableGame{
|
||||||
|
CatalogItem: gamedomain.CatalogItem{AppCode: "lalu", GameID: "private_slot", PlatformCode: "demo", ProviderGameID: "private_1", GameName: "Private", Status: gamedomain.StatusActive},
|
||||||
|
PlatformStatus: gamedomain.StatusActive,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
svc := New(Config{LaunchSessionTTL: 15 * time.Minute}, repo, &fakeWallet{}, &fakeUser{})
|
||||||
|
|
||||||
|
_, err := svc.LaunchGame(context.Background(), LaunchCommand{AppCode: "lalu", UserID: 42, GameID: "private_slot"})
|
||||||
|
if !xerr.IsCode(err, xerr.NotFound) {
|
||||||
|
t.Fatalf("user outside enabled whitelist must not discover or launch game, got %v", err)
|
||||||
|
}
|
||||||
|
if len(repo.sessions) != 0 {
|
||||||
|
t.Fatalf("denied launch must not create a session: %+v", repo.sessions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestListRecentGamesNormalizesQueryAndDefaultsLimit(t *testing.T) {
|
func TestListRecentGamesNormalizesQueryAndDefaultsLimit(t *testing.T) {
|
||||||
repo := &fakeRepository{
|
repo := &fakeRepository{
|
||||||
recentGames: []gamedomain.AppGame{
|
recentGames: []gamedomain.AppGame{
|
||||||
@ -1980,6 +2000,7 @@ type fakeRepository struct {
|
|||||||
lastListPlatformsStatus string
|
lastListPlatformsStatus string
|
||||||
deliveredEvents []string
|
deliveredEvents []string
|
||||||
failedEvents []string
|
failedEvents []string
|
||||||
|
accessAllowed *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeRepository) Ping(context.Context) error { return nil }
|
func (f *fakeRepository) Ping(context.Context) error { return nil }
|
||||||
@ -2139,6 +2160,24 @@ func (f *fakeRepository) SetGameStatus(context.Context, string, string, string,
|
|||||||
func (f *fakeRepository) DeleteCatalog(context.Context, string, string) error {
|
func (f *fakeRepository) DeleteCatalog(context.Context, string, string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (f *fakeRepository) CanUserAccessGame(context.Context, string, string, int64) (bool, error) {
|
||||||
|
if f.accessAllowed != nil {
|
||||||
|
return *f.accessAllowed, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
func (f *fakeRepository) SetGameWhitelistEnabled(context.Context, string, string, bool, int64) (gamedomain.CatalogItem, error) {
|
||||||
|
return gamedomain.CatalogItem{}, nil
|
||||||
|
}
|
||||||
|
func (f *fakeRepository) ListGameWhitelistUsers(context.Context, string, string, int32) ([]gamedomain.WhitelistUser, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (f *fakeRepository) AddGameWhitelistUser(_ context.Context, item gamedomain.WhitelistUser) (gamedomain.WhitelistUser, error) {
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
func (f *fakeRepository) DeleteGameWhitelistUser(context.Context, string, string, int64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type fakeWallet struct {
|
type fakeWallet struct {
|
||||||
lastApply *walletv1.ApplyGameCoinChangeRequest
|
lastApply *walletv1.ApplyGameCoinChangeRequest
|
||||||
|
|||||||
@ -93,6 +93,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
|
|||||||
safe_height INT NOT NULL DEFAULT 0,
|
safe_height INT NOT NULL DEFAULT 0,
|
||||||
min_coin BIGINT NOT NULL DEFAULT 0,
|
min_coin BIGINT NOT NULL DEFAULT 0,
|
||||||
status VARCHAR(32) NOT NULL,
|
status VARCHAR(32) NOT NULL,
|
||||||
|
whitelist_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
sort_order INT NOT NULL DEFAULT 0,
|
sort_order INT NOT NULL DEFAULT 0,
|
||||||
tags JSON NULL,
|
tags JSON NULL,
|
||||||
created_at_ms BIGINT NOT NULL,
|
created_at_ms BIGINT NOT NULL,
|
||||||
@ -101,6 +102,15 @@ func (r *Repository) Migrate(ctx context.Context) error {
|
|||||||
UNIQUE KEY uk_game_provider(app_code, platform_code, provider_game_id),
|
UNIQUE KEY uk_game_provider(app_code, platform_code, provider_game_id),
|
||||||
KEY idx_game_platform_status(app_code, platform_code, status, sort_order)
|
KEY idx_game_platform_status(app_code, platform_code, status, sort_order)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS game_user_whitelist (
|
||||||
|
app_code VARCHAR(32) NOT NULL,
|
||||||
|
game_id VARCHAR(96) NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
created_by_admin_id BIGINT NOT NULL DEFAULT 0,
|
||||||
|
created_at_ms BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY(app_code, game_id, user_id),
|
||||||
|
KEY idx_game_whitelist_game_time(app_code, game_id, created_at_ms, user_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
||||||
`CREATE TABLE IF NOT EXISTS game_display_rules (
|
`CREATE TABLE IF NOT EXISTS game_display_rules (
|
||||||
app_code VARCHAR(32) NOT NULL,
|
app_code VARCHAR(32) NOT NULL,
|
||||||
rule_id VARCHAR(96) NOT NULL,
|
rule_id VARCHAR(96) NOT NULL,
|
||||||
@ -494,6 +504,10 @@ func (r *Repository) Migrate(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// game_catalog 线上已有数据量很小;新增带默认值的布尔列不会回填业务数据,默认关闭也保持历史可见范围。
|
||||||
|
if err := r.ensureColumn(ctx, "game_catalog", "whitelist_enabled", "whitelist_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER status"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := r.ensureColumn(ctx, "game_platforms", "adapter_type", "adapter_type VARCHAR(64) NOT NULL DEFAULT '' AFTER api_base_url"); err != nil {
|
if err := r.ensureColumn(ctx, "game_platforms", "adapter_type", "adapter_type VARCHAR(64) NOT NULL DEFAULT '' AFTER api_base_url"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -702,10 +716,15 @@ func (r *Repository) ListGames(ctx context.Context, query gameservice.ListGamesQ
|
|||||||
AND (r.region_id = 0 OR r.region_id = ?)
|
AND (r.region_id = 0 OR r.region_id = ?)
|
||||||
AND (r.language = '' OR r.language = ?)
|
AND (r.language = '' OR r.language = ?)
|
||||||
AND (r.platform = '' OR r.platform = ?)
|
AND (r.platform = '' OR r.platform = ?)
|
||||||
|
AND (c.whitelist_enabled = 0 OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM game_user_whitelist w
|
||||||
|
WHERE w.app_code = c.app_code AND w.game_id = c.game_id AND w.user_id = ?
|
||||||
|
))
|
||||||
GROUP BY c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category,
|
GROUP BY c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category,
|
||||||
c.launch_mode, c.orientation, c.safe_height, c.min_coin, c.status, p.status, c.sort_order, p.sort_order
|
c.launch_mode, c.orientation, c.safe_height, c.min_coin, c.status, p.status, c.sort_order, p.sort_order
|
||||||
ORDER BY display_sort ASC, p.sort_order ASC, c.sort_order ASC, c.game_id ASC`,
|
ORDER BY display_sort ASC, p.sort_order ASC, c.sort_order ASC, c.game_id ASC`,
|
||||||
appcode.Normalize(query.AppCode), query.Scene, query.RegionID, query.Language, query.ClientPlatform,
|
appcode.Normalize(query.AppCode), query.Scene, query.RegionID, query.Language, query.ClientPlatform, query.UserID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -758,13 +777,18 @@ func (r *Repository) ListRecentGames(ctx context.Context, query gameservice.List
|
|||||||
AND (r.region_id = 0 OR r.region_id = ?)
|
AND (r.region_id = 0 OR r.region_id = ?)
|
||||||
AND (r.language = '' OR r.language = ?)
|
AND (r.language = '' OR r.language = ?)
|
||||||
AND (r.platform = '' OR r.platform = ?)
|
AND (r.platform = '' OR r.platform = ?)
|
||||||
|
AND (c.whitelist_enabled = 0 OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM game_user_whitelist w
|
||||||
|
WHERE w.app_code = c.app_code AND w.game_id = c.game_id AND w.user_id = ?
|
||||||
|
))
|
||||||
GROUP BY c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category,
|
GROUP BY c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category,
|
||||||
c.launch_mode, c.orientation, c.safe_height, c.min_coin, c.status, p.status,
|
c.launch_mode, c.orientation, c.safe_height, c.min_coin, c.status, p.status,
|
||||||
c.sort_order, p.sort_order, recent.last_played_at_ms
|
c.sort_order, p.sort_order, recent.last_played_at_ms
|
||||||
ORDER BY recent.last_played_at_ms DESC, display_sort ASC, p.sort_order ASC, c.sort_order ASC, c.game_id ASC
|
ORDER BY recent.last_played_at_ms DESC, display_sort ASC, p.sort_order ASC, c.sort_order ASC, c.game_id ASC
|
||||||
LIMIT ?`,
|
LIMIT ?`,
|
||||||
appcode.Normalize(query.AppCode), query.UserID, query.Scene,
|
appcode.Normalize(query.AppCode), query.UserID, query.Scene,
|
||||||
appcode.Normalize(query.AppCode), query.Scene, query.RegionID, query.Language, query.ClientPlatform, pageSize,
|
appcode.Normalize(query.AppCode), query.Scene, query.RegionID, query.Language, query.ClientPlatform, query.UserID, pageSize,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -816,6 +840,31 @@ func (r *Repository) GetLaunchableGame(ctx context.Context, appCode string, game
|
|||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanUserAccessGame 在真正创建启动会话前做精确授权;关闭白名单时直接放行,开启时只命中复合主键,不扫描成员表。
|
||||||
|
func (r *Repository) CanUserAccessGame(ctx context.Context, appCode string, gameID string, userID int64) (bool, error) {
|
||||||
|
row := r.db.QueryRowContext(ctx,
|
||||||
|
`SELECT CASE
|
||||||
|
WHEN c.whitelist_enabled = 0 THEN 1
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1 FROM game_user_whitelist w
|
||||||
|
WHERE w.app_code = c.app_code AND w.game_id = c.game_id AND w.user_id = ?
|
||||||
|
) THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
FROM game_catalog c
|
||||||
|
WHERE c.app_code = ? AND c.game_id = ?`,
|
||||||
|
userID, appcode.Normalize(appCode), strings.TrimSpace(gameID),
|
||||||
|
)
|
||||||
|
var allowed bool
|
||||||
|
if err := row.Scan(&allowed); err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return false, xerr.New(xerr.NotFound, "game not found")
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return allowed, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Repository) GetPlatform(ctx context.Context, appCode string, platformCode string) (gamedomain.Platform, error) {
|
func (r *Repository) GetPlatform(ctx context.Context, appCode string, platformCode string) (gamedomain.Platform, error) {
|
||||||
// 回调入口只知道 platform_code,必须按 app_code 隔离查询,防止多租户平台编码冲突。
|
// 回调入口只知道 platform_code,必须按 app_code 隔离查询,防止多租户平台编码冲突。
|
||||||
row := r.db.QueryRowContext(ctx,
|
row := r.db.QueryRowContext(ctx,
|
||||||
@ -1598,7 +1647,8 @@ func (r *Repository) UpsertPlatform(ctx context.Context, platform gamedomain.Pla
|
|||||||
|
|
||||||
func (r *Repository) ListCatalog(ctx context.Context, query gameservice.ListCatalogQuery) ([]gamedomain.CatalogItem, string, error) {
|
func (r *Repository) ListCatalog(ctx context.Context, query gameservice.ListCatalogQuery) ([]gamedomain.CatalogItem, string, error) {
|
||||||
sqlText := `SELECT app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url,
|
sqlText := `SELECT app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url,
|
||||||
launch_mode, orientation, safe_height, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms
|
launch_mode, orientation, safe_height, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms,
|
||||||
|
whitelist_enabled
|
||||||
FROM game_catalog WHERE app_code = ?`
|
FROM game_catalog WHERE app_code = ?`
|
||||||
args := []any{appcode.Normalize(query.AppCode)}
|
args := []any{appcode.Normalize(query.AppCode)}
|
||||||
if strings.TrimSpace(query.PlatformCode) != "" {
|
if strings.TrimSpace(query.PlatformCode) != "" {
|
||||||
@ -1668,7 +1718,11 @@ func (r *Repository) UpsertCatalog(ctx context.Context, item gamedomain.CatalogI
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
return gamedomain.CatalogItem{}, err
|
return gamedomain.CatalogItem{}, err
|
||||||
}
|
}
|
||||||
return item, tx.Commit()
|
if err := tx.Commit(); err != nil {
|
||||||
|
return gamedomain.CatalogItem{}, err
|
||||||
|
}
|
||||||
|
// whitelist_enabled 只允许专用管理接口修改;更新目录后重新读取真实行,避免响应把既有白名单状态误报为 false。
|
||||||
|
return r.getCatalog(ctx, item.AppCode, item.GameID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultVoiceRoomRuleID(gameID string) string {
|
func defaultVoiceRoomRuleID(gameID string) string {
|
||||||
@ -1690,6 +1744,96 @@ func (r *Repository) SetGameStatus(ctx context.Context, appCode string, gameID s
|
|||||||
return r.getCatalog(ctx, appCode, gameID)
|
return r.getCatalog(ctx, appCode, gameID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Repository) SetGameWhitelistEnabled(ctx context.Context, appCode string, gameID string, enabled bool, nowMS int64) (gamedomain.CatalogItem, error) {
|
||||||
|
result, err := r.db.ExecContext(ctx,
|
||||||
|
`UPDATE game_catalog SET whitelist_enabled = ?, updated_at_ms = ? WHERE app_code = ? AND game_id = ?`,
|
||||||
|
enabled, nowMS, appcode.Normalize(appCode), strings.TrimSpace(gameID),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return gamedomain.CatalogItem{}, err
|
||||||
|
}
|
||||||
|
if affected, err := result.RowsAffected(); err != nil {
|
||||||
|
return gamedomain.CatalogItem{}, err
|
||||||
|
} else if affected == 0 {
|
||||||
|
return gamedomain.CatalogItem{}, xerr.New(xerr.NotFound, "game not found")
|
||||||
|
}
|
||||||
|
return r.getCatalog(ctx, appCode, gameID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListGameWhitelistUsers(ctx context.Context, appCode string, gameID string, pageSize int32) ([]gamedomain.WhitelistUser, error) {
|
||||||
|
if pageSize <= 0 || pageSize > 200 {
|
||||||
|
pageSize = 200
|
||||||
|
}
|
||||||
|
// 管理页默认展示当前游戏成员;覆盖索引按 game 范围读取并硬限制数量,避免成员增长后出现无界查询。
|
||||||
|
rows, err := r.db.QueryContext(ctx,
|
||||||
|
`SELECT app_code, game_id, user_id, created_by_admin_id, created_at_ms
|
||||||
|
FROM game_user_whitelist FORCE INDEX (idx_game_whitelist_game_time)
|
||||||
|
WHERE app_code = ? AND game_id = ?
|
||||||
|
ORDER BY created_at_ms DESC, user_id DESC
|
||||||
|
LIMIT ?`,
|
||||||
|
appcode.Normalize(appCode), strings.TrimSpace(gameID), pageSize,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := make([]gamedomain.WhitelistUser, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var item gamedomain.WhitelistUser
|
||||||
|
if err := rows.Scan(&item.AppCode, &item.GameID, &item.UserID, &item.CreatedByAdminID, &item.CreatedAtMS); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
return items, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) AddGameWhitelistUser(ctx context.Context, item gamedomain.WhitelistUser) (gamedomain.WhitelistUser, error) {
|
||||||
|
item.AppCode = appcode.Normalize(item.AppCode)
|
||||||
|
item.GameID = strings.TrimSpace(item.GameID)
|
||||||
|
if item.UserID <= 0 || item.GameID == "" {
|
||||||
|
return gamedomain.WhitelistUser{}, xerr.New(xerr.InvalidArgument, "game whitelist user is incomplete")
|
||||||
|
}
|
||||||
|
if item.CreatedAtMS <= 0 {
|
||||||
|
item.CreatedAtMS = time.Now().UnixMilli()
|
||||||
|
}
|
||||||
|
// INSERT ... SELECT 同时验证目录存在;重复添加只更新操作来源和时间,复合主键保证不会产生重复成员。
|
||||||
|
result, err := r.db.ExecContext(ctx,
|
||||||
|
`INSERT INTO game_user_whitelist (app_code, game_id, user_id, created_by_admin_id, created_at_ms)
|
||||||
|
SELECT app_code, game_id, ?, ?, ? FROM game_catalog WHERE app_code = ? AND game_id = ?
|
||||||
|
ON DUPLICATE KEY UPDATE created_by_admin_id = VALUES(created_by_admin_id), created_at_ms = VALUES(created_at_ms)`,
|
||||||
|
item.UserID, item.CreatedByAdminID, item.CreatedAtMS, item.AppCode, item.GameID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return gamedomain.WhitelistUser{}, err
|
||||||
|
}
|
||||||
|
if affected, err := result.RowsAffected(); err != nil {
|
||||||
|
return gamedomain.WhitelistUser{}, err
|
||||||
|
} else if affected == 0 {
|
||||||
|
return gamedomain.WhitelistUser{}, xerr.New(xerr.NotFound, "game not found")
|
||||||
|
}
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) DeleteGameWhitelistUser(ctx context.Context, appCode string, gameID string, userID int64) error {
|
||||||
|
if userID <= 0 {
|
||||||
|
return xerr.New(xerr.InvalidArgument, "user_id is required")
|
||||||
|
}
|
||||||
|
result, err := r.db.ExecContext(ctx,
|
||||||
|
`DELETE FROM game_user_whitelist WHERE app_code = ? AND game_id = ? AND user_id = ?`,
|
||||||
|
appcode.Normalize(appCode), strings.TrimSpace(gameID), userID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if affected, err := result.RowsAffected(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if affected == 0 {
|
||||||
|
return xerr.New(xerr.NotFound, "game whitelist user not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Repository) DeleteCatalog(ctx context.Context, appCode string, gameID string) error {
|
func (r *Repository) DeleteCatalog(ctx context.Context, appCode string, gameID string) error {
|
||||||
app := appcode.Normalize(appCode)
|
app := appcode.Normalize(appCode)
|
||||||
id := strings.TrimSpace(gameID)
|
id := strings.TrimSpace(gameID)
|
||||||
@ -1719,13 +1863,17 @@ func (r *Repository) DeleteCatalog(ctx context.Context, appCode string, gameID s
|
|||||||
if _, err := tx.ExecContext(ctx, `DELETE FROM game_display_rules WHERE app_code = ? AND game_id = ?`, app, id); err != nil {
|
if _, err := tx.ExecContext(ctx, `DELETE FROM game_display_rules WHERE app_code = ? AND game_id = ?`, app, id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, err := tx.ExecContext(ctx, `DELETE FROM game_user_whitelist WHERE app_code = ? AND game_id = ?`, app, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) getCatalog(ctx context.Context, appCode string, gameID string) (gamedomain.CatalogItem, error) {
|
func (r *Repository) getCatalog(ctx context.Context, appCode string, gameID string) (gamedomain.CatalogItem, error) {
|
||||||
row := r.db.QueryRowContext(ctx,
|
row := r.db.QueryRowContext(ctx,
|
||||||
`SELECT app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url,
|
`SELECT app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url,
|
||||||
launch_mode, orientation, safe_height, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms
|
launch_mode, orientation, safe_height, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms,
|
||||||
|
whitelist_enabled
|
||||||
FROM game_catalog WHERE app_code = ? AND game_id = ?`,
|
FROM game_catalog WHERE app_code = ? AND game_id = ?`,
|
||||||
appcode.Normalize(appCode), strings.TrimSpace(gameID),
|
appcode.Normalize(appCode), strings.TrimSpace(gameID),
|
||||||
)
|
)
|
||||||
@ -1790,7 +1938,7 @@ func levelEventOutboxSelectSQL() string {
|
|||||||
func scanCatalog(scanner catalogScanner) (gamedomain.CatalogItem, error) {
|
func scanCatalog(scanner catalogScanner) (gamedomain.CatalogItem, error) {
|
||||||
var item gamedomain.CatalogItem
|
var item gamedomain.CatalogItem
|
||||||
var tagsJSON string
|
var tagsJSON string
|
||||||
if err := scanner.Scan(&item.AppCode, &item.GameID, &item.PlatformCode, &item.ProviderGameID, &item.GameName, &item.Category, &item.IconURL, &item.CoverURL, &item.LaunchMode, &item.Orientation, &item.SafeHeight, &item.MinCoin, &item.Status, &item.SortOrder, &tagsJSON, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil {
|
if err := scanner.Scan(&item.AppCode, &item.GameID, &item.PlatformCode, &item.ProviderGameID, &item.GameName, &item.Category, &item.IconURL, &item.CoverURL, &item.LaunchMode, &item.Orientation, &item.SafeHeight, &item.MinCoin, &item.Status, &item.SortOrder, &tagsJSON, &item.CreatedAtMS, &item.UpdatedAtMS, &item.WhitelistEnabled); err != nil {
|
||||||
return gamedomain.CatalogItem{}, err
|
return gamedomain.CatalogItem{}, err
|
||||||
}
|
}
|
||||||
_ = json.Unmarshal([]byte(tagsJSON), &item.Tags)
|
_ = json.Unmarshal([]byte(tagsJSON), &item.Tags)
|
||||||
|
|||||||
@ -362,6 +362,66 @@ func TestAppGameListsExcludeBuiltInSelfGames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGameWhitelistFiltersCatalogAndRecentListsAndControlsLaunchAccess(t *testing.T) {
|
||||||
|
caller := mysqlschema.CallerFile(t, 1)
|
||||||
|
schema := mysqlschema.New(t, mysqlschema.Config{
|
||||||
|
InitDBPath: mysqlschema.InitDBPath(t, caller, "../../../deploy/mysql/initdb/001_game_service.sql"),
|
||||||
|
DatabasePrefix: "hy_game_test",
|
||||||
|
})
|
||||||
|
ctx := appcode.WithContext(context.Background(), "lalu")
|
||||||
|
repo, err := Open(ctx, schema.DSN)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open repository failed: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = repo.Close() })
|
||||||
|
|
||||||
|
nowMS := int64(1781200000000)
|
||||||
|
if _, err := repo.UpsertPlatform(ctx, gamedomain.Platform{AppCode: "lalu", PlatformCode: "demo", PlatformName: "Demo", Status: gamedomain.StatusActive, AdapterType: gamedomain.AdapterDemo, CreatedAtMS: nowMS, UpdatedAtMS: nowMS}); err != nil {
|
||||||
|
t.Fatalf("upsert platform failed: %v", err)
|
||||||
|
}
|
||||||
|
item := gamedomain.CatalogItem{AppCode: "lalu", GameID: "private_slot", PlatformCode: "demo", ProviderGameID: "private_1", GameName: "Private Slot", LaunchMode: gamedomain.LaunchModeH5Popup, Orientation: "portrait", Status: gamedomain.StatusActive}
|
||||||
|
if _, err := repo.UpsertCatalog(ctx, item); err != nil {
|
||||||
|
t.Fatalf("upsert catalog failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := repo.CreateLaunchSession(ctx, gamedomain.LaunchSession{AppCode: "lalu", SessionID: "private_session", UserID: 42, Scene: gamedomain.SceneVoiceRoom, PlatformCode: "demo", GameID: item.GameID, ProviderGameID: item.ProviderGameID, LaunchTokenHash: "private_hash", Status: gamedomain.SessionActive, ExpiresAtMS: nowMS + 60000, CreatedAtMS: nowMS, UpdatedAtMS: nowMS}); err != nil {
|
||||||
|
t.Fatalf("create launch session failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := repo.SetGameWhitelistEnabled(ctx, "lalu", item.GameID, true, nowMS); err != nil {
|
||||||
|
t.Fatalf("enable whitelist failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listQuery := gameservice.ListGamesQuery{AppCode: "lalu", UserID: 42, Scene: gamedomain.SceneVoiceRoom}
|
||||||
|
if games, err := repo.ListGames(ctx, listQuery); err != nil || len(games) != 0 {
|
||||||
|
t.Fatalf("non-member catalog list must hide game, games=%+v err=%v", games, err)
|
||||||
|
}
|
||||||
|
if recent, err := repo.ListRecentGames(ctx, gameservice.ListRecentGamesQuery{AppCode: "lalu", UserID: 42, Scene: gamedomain.SceneVoiceRoom, PageSize: 10}); err != nil || len(recent) != 0 {
|
||||||
|
t.Fatalf("non-member recent list must hide game, games=%+v err=%v", recent, err)
|
||||||
|
}
|
||||||
|
if allowed, err := repo.CanUserAccessGame(ctx, "lalu", item.GameID, 42); err != nil || allowed {
|
||||||
|
t.Fatalf("non-member launch access mismatch: allowed=%v err=%v", allowed, err)
|
||||||
|
}
|
||||||
|
member, err := repo.AddGameWhitelistUser(ctx, gamedomain.WhitelistUser{AppCode: "lalu", GameID: item.GameID, UserID: 42, CreatedByAdminID: 7, CreatedAtMS: nowMS + 1})
|
||||||
|
if err != nil || member.UserID != 42 {
|
||||||
|
t.Fatalf("add whitelist member failed: member=%+v err=%v", member, err)
|
||||||
|
}
|
||||||
|
if games, err := repo.ListGames(ctx, listQuery); err != nil || len(games) != 1 || games[0].GameID != item.GameID {
|
||||||
|
t.Fatalf("member catalog list mismatch: games=%+v err=%v", games, err)
|
||||||
|
}
|
||||||
|
if allowed, err := repo.CanUserAccessGame(ctx, "lalu", item.GameID, 42); err != nil || !allowed {
|
||||||
|
t.Fatalf("member launch access mismatch: allowed=%v err=%v", allowed, err)
|
||||||
|
}
|
||||||
|
members, err := repo.ListGameWhitelistUsers(ctx, "lalu", item.GameID, 200)
|
||||||
|
if err != nil || len(members) != 1 || members[0].CreatedByAdminID != 7 {
|
||||||
|
t.Fatalf("whitelist member list mismatch: members=%+v err=%v", members, err)
|
||||||
|
}
|
||||||
|
if err := repo.DeleteGameWhitelistUser(ctx, "lalu", item.GameID, 42); err != nil {
|
||||||
|
t.Fatalf("delete whitelist member failed: %v", err)
|
||||||
|
}
|
||||||
|
if allowed, err := repo.CanUserAccessGame(ctx, "lalu", item.GameID, 42); err != nil || allowed {
|
||||||
|
t.Fatalf("removed member must lose launch access: allowed=%v err=%v", allowed, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarkOrderSucceededCreatesClaimableLevelOutboxEvent(t *testing.T) {
|
func TestMarkOrderSucceededCreatesClaimableLevelOutboxEvent(t *testing.T) {
|
||||||
caller := mysqlschema.CallerFile(t, 1)
|
caller := mysqlschema.CallerFile(t, 1)
|
||||||
schema := mysqlschema.New(t, mysqlschema.Config{
|
schema := mysqlschema.New(t, mysqlschema.Config{
|
||||||
|
|||||||
@ -493,6 +493,57 @@ func (s *Server) DeleteCatalog(ctx context.Context, req *gamev1.DeleteCatalogReq
|
|||||||
return &gamev1.DeleteCatalogResponse{ServerTimeMs: time.Now().UnixMilli()}, nil
|
return &gamev1.DeleteCatalogResponse{ServerTimeMs: time.Now().UnixMilli()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) SetGameWhitelistEnabled(ctx context.Context, req *gamev1.SetGameWhitelistEnabledRequest) (*gamev1.CatalogResponse, error) {
|
||||||
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||||||
|
item, err := s.svcRepository().SetGameWhitelistEnabled(
|
||||||
|
ctx,
|
||||||
|
req.GetMeta().GetAppCode(),
|
||||||
|
strings.TrimSpace(req.GetGameId()),
|
||||||
|
req.GetEnabled(),
|
||||||
|
time.Now().UnixMilli(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerr.ToGRPCError(err)
|
||||||
|
}
|
||||||
|
return &gamev1.CatalogResponse{Game: catalogToProto(item), ServerTimeMs: time.Now().UnixMilli()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListGameWhitelistUsers(ctx context.Context, req *gamev1.ListGameWhitelistUsersRequest) (*gamev1.ListGameWhitelistUsersResponse, error) {
|
||||||
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||||||
|
items, err := s.svcRepository().ListGameWhitelistUsers(ctx, req.GetMeta().GetAppCode(), strings.TrimSpace(req.GetGameId()), req.GetPageSize())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerr.ToGRPCError(err)
|
||||||
|
}
|
||||||
|
resp := &gamev1.ListGameWhitelistUsersResponse{Users: make([]*gamev1.GameWhitelistUser, 0, len(items)), ServerTimeMs: time.Now().UnixMilli()}
|
||||||
|
for _, item := range items {
|
||||||
|
resp.Users = append(resp.Users, whitelistUserToProto(item))
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AddGameWhitelistUser(ctx context.Context, req *gamev1.AddGameWhitelistUserRequest) (*gamev1.GameWhitelistUserResponse, error) {
|
||||||
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||||||
|
item, err := s.svcRepository().AddGameWhitelistUser(ctx, gamedomain.WhitelistUser{
|
||||||
|
AppCode: req.GetMeta().GetAppCode(),
|
||||||
|
GameID: strings.TrimSpace(req.GetGameId()),
|
||||||
|
UserID: req.GetUserId(),
|
||||||
|
CreatedByAdminID: req.GetMeta().GetActorUserId(),
|
||||||
|
CreatedAtMS: time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerr.ToGRPCError(err)
|
||||||
|
}
|
||||||
|
return &gamev1.GameWhitelistUserResponse{User: whitelistUserToProto(item), ServerTimeMs: time.Now().UnixMilli()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) DeleteGameWhitelistUser(ctx context.Context, req *gamev1.DeleteGameWhitelistUserRequest) (*gamev1.DeleteGameWhitelistUserResponse, error) {
|
||||||
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||||||
|
if err := s.svcRepository().DeleteGameWhitelistUser(ctx, req.GetMeta().GetAppCode(), strings.TrimSpace(req.GetGameId()), req.GetUserId()); err != nil {
|
||||||
|
return nil, xerr.ToGRPCError(err)
|
||||||
|
}
|
||||||
|
return &gamev1.DeleteGameWhitelistUserResponse{ServerTimeMs: time.Now().UnixMilli()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) ListSelfGames(ctx context.Context, req *gamev1.ListSelfGamesRequest) (*gamev1.ListSelfGamesResponse, error) {
|
func (s *Server) ListSelfGames(ctx context.Context, req *gamev1.ListSelfGamesRequest) (*gamev1.ListSelfGamesResponse, error) {
|
||||||
if s == nil || s.diceSvc == nil {
|
if s == nil || s.diceSvc == nil {
|
||||||
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured"))
|
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured"))
|
||||||
@ -731,6 +782,21 @@ func (unavailableRepository) SetGameStatus(context.Context, string, string, stri
|
|||||||
func (unavailableRepository) DeleteCatalog(context.Context, string, string) error {
|
func (unavailableRepository) DeleteCatalog(context.Context, string, string) error {
|
||||||
return xerr.New(xerr.Unavailable, "game repository is not configured")
|
return xerr.New(xerr.Unavailable, "game repository is not configured")
|
||||||
}
|
}
|
||||||
|
func (unavailableRepository) CanUserAccessGame(context.Context, string, string, int64) (bool, error) {
|
||||||
|
return false, xerr.New(xerr.Unavailable, "game repository is not configured")
|
||||||
|
}
|
||||||
|
func (unavailableRepository) SetGameWhitelistEnabled(context.Context, string, string, bool, int64) (gamedomain.CatalogItem, error) {
|
||||||
|
return gamedomain.CatalogItem{}, xerr.New(xerr.Unavailable, "game repository is not configured")
|
||||||
|
}
|
||||||
|
func (unavailableRepository) ListGameWhitelistUsers(context.Context, string, string, int32) ([]gamedomain.WhitelistUser, error) {
|
||||||
|
return nil, xerr.New(xerr.Unavailable, "game repository is not configured")
|
||||||
|
}
|
||||||
|
func (unavailableRepository) AddGameWhitelistUser(context.Context, gamedomain.WhitelistUser) (gamedomain.WhitelistUser, error) {
|
||||||
|
return gamedomain.WhitelistUser{}, xerr.New(xerr.Unavailable, "game repository is not configured")
|
||||||
|
}
|
||||||
|
func (unavailableRepository) DeleteGameWhitelistUser(context.Context, string, string, int64) error {
|
||||||
|
return xerr.New(xerr.Unavailable, "game repository is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
func appGameToProto(item gamedomain.AppGame) *gamev1.AppGame {
|
func appGameToProto(item gamedomain.AppGame) *gamev1.AppGame {
|
||||||
return &gamev1.AppGame{
|
return &gamev1.AppGame{
|
||||||
@ -1051,23 +1117,34 @@ func platformFromProto(item *gamev1.GamePlatform) gamedomain.Platform {
|
|||||||
|
|
||||||
func catalogToProto(item gamedomain.CatalogItem) *gamev1.GameCatalogItem {
|
func catalogToProto(item gamedomain.CatalogItem) *gamev1.GameCatalogItem {
|
||||||
return &gamev1.GameCatalogItem{
|
return &gamev1.GameCatalogItem{
|
||||||
AppCode: item.AppCode,
|
AppCode: item.AppCode,
|
||||||
GameId: item.GameID,
|
GameId: item.GameID,
|
||||||
PlatformCode: item.PlatformCode,
|
PlatformCode: item.PlatformCode,
|
||||||
ProviderGameId: item.ProviderGameID,
|
ProviderGameId: item.ProviderGameID,
|
||||||
GameName: item.GameName,
|
GameName: item.GameName,
|
||||||
Category: item.Category,
|
Category: item.Category,
|
||||||
IconUrl: item.IconURL,
|
IconUrl: item.IconURL,
|
||||||
CoverUrl: item.CoverURL,
|
CoverUrl: item.CoverURL,
|
||||||
LaunchMode: item.LaunchMode,
|
LaunchMode: item.LaunchMode,
|
||||||
Orientation: item.Orientation,
|
Orientation: item.Orientation,
|
||||||
SafeHeight: item.SafeHeight,
|
SafeHeight: item.SafeHeight,
|
||||||
MinCoin: item.MinCoin,
|
MinCoin: item.MinCoin,
|
||||||
Status: item.Status,
|
Status: item.Status,
|
||||||
SortOrder: item.SortOrder,
|
SortOrder: item.SortOrder,
|
||||||
Tags: item.Tags,
|
Tags: item.Tags,
|
||||||
CreatedAtMs: item.CreatedAtMS,
|
CreatedAtMs: item.CreatedAtMS,
|
||||||
UpdatedAtMs: item.UpdatedAtMS,
|
UpdatedAtMs: item.UpdatedAtMS,
|
||||||
|
WhitelistEnabled: item.WhitelistEnabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func whitelistUserToProto(item gamedomain.WhitelistUser) *gamev1.GameWhitelistUser {
|
||||||
|
return &gamev1.GameWhitelistUser{
|
||||||
|
AppCode: item.AppCode,
|
||||||
|
GameId: item.GameID,
|
||||||
|
UserId: item.UserID,
|
||||||
|
CreatedByAdminId: item.CreatedByAdminID,
|
||||||
|
CreatedAtMs: item.CreatedAtMS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1076,21 +1153,22 @@ func catalogFromProto(item *gamev1.GameCatalogItem) gamedomain.CatalogItem {
|
|||||||
return gamedomain.CatalogItem{}
|
return gamedomain.CatalogItem{}
|
||||||
}
|
}
|
||||||
return gamedomain.CatalogItem{
|
return gamedomain.CatalogItem{
|
||||||
AppCode: item.GetAppCode(),
|
AppCode: item.GetAppCode(),
|
||||||
GameID: item.GetGameId(),
|
GameID: item.GetGameId(),
|
||||||
PlatformCode: item.GetPlatformCode(),
|
PlatformCode: item.GetPlatformCode(),
|
||||||
ProviderGameID: item.GetProviderGameId(),
|
ProviderGameID: item.GetProviderGameId(),
|
||||||
GameName: item.GetGameName(),
|
GameName: item.GetGameName(),
|
||||||
Category: item.GetCategory(),
|
Category: item.GetCategory(),
|
||||||
IconURL: item.GetIconUrl(),
|
IconURL: item.GetIconUrl(),
|
||||||
CoverURL: item.GetCoverUrl(),
|
CoverURL: item.GetCoverUrl(),
|
||||||
LaunchMode: item.GetLaunchMode(),
|
LaunchMode: item.GetLaunchMode(),
|
||||||
Orientation: item.GetOrientation(),
|
Orientation: item.GetOrientation(),
|
||||||
SafeHeight: item.GetSafeHeight(),
|
SafeHeight: item.GetSafeHeight(),
|
||||||
MinCoin: item.GetMinCoin(),
|
MinCoin: item.GetMinCoin(),
|
||||||
Status: item.GetStatus(),
|
Status: item.GetStatus(),
|
||||||
SortOrder: item.GetSortOrder(),
|
SortOrder: item.GetSortOrder(),
|
||||||
Tags: item.GetTags(),
|
Tags: item.GetTags(),
|
||||||
|
WhitelistEnabled: item.GetWhitelistEnabled(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user