Compare commits

...

6 Commits

Author SHA1 Message Date
zhx
9c056b9cbf Merge branch 'main' of gitea.haiyihy.com:hy/hyapp-server into codex/hotgame-compat-main-20260610 2026-06-18 17:29:58 +08:00
zhx
db43da8c85 修复机器人创建和转盘 2026-06-18 17:13:56 +08:00
zhx
a0c9d055f0 代理活动修复 2026-06-18 14:19:47 +08:00
zhx
c93a30f3db fix: allow robot init active appearance resources 2026-06-18 10:35:13 +08:00
zhx
0d111d9a88 机器人 2026-06-17 23:13:08 +08:00
zhx
da64d5f189 房间列表 2026-06-17 13:15:05 +08:00
155 changed files with 39866 additions and 16504 deletions

View File

@ -193,6 +193,7 @@ docs/flutter对接/Flutter App 通知与钱包余额对接.md
| `hyapp_room_outbox` | `hyapp-room-outbox-producer` | `hyapp-activity-room-outbox` | activity-service 消费 `RoomGiftSent``RoomRocketIgnited` 等房间事实 |
| `hyapp_room_outbox` | `hyapp-room-outbox-producer` | `hyapp-notice-room-outbox` | notice-service 消费 `RoomUserKicked` 等私有通知事实 |
| `hyapp_room_outbox` | `hyapp-room-outbox-producer` | `hyapp-room-im-bridge` | room-service IM bridge 消费房间群系统消息和群成员控制事实 |
| `hyapp_robot_room_outbox` | `hyapp-room-robot-outbox-producer` | `hyapp-test-room-robot-im-bridge` / `hyapp-room-robot-im-bridge` | room-service IM bridge 只消费机器人房间礼物、热度和麦位热度展示事实;本地/testbox 用 test group线上用正式 group |
| `hyapp_room_outbox` | `hyapp-room-outbox-producer` | `hyapp-user-mictime-room-outbox` | user-service 消费 `RoomMicChanged` 并更新用户麦时读模型 |
| `hyapp_room_outbox` | `hyapp-room-outbox-producer` | `hyapp-statistics-room-outbox` | statistics-service 消费 `RoomGiftSent``RoomUserJoined` 并更新聚合表 |
| `hyapp_user_outbox` | `hyapp-user-outbox-producer` | `hyapp-statistics-user-outbox` | statistics-service 消费 `UserRegistered` 并更新注册 cohort |

File diff suppressed because it is too large Load Diff

View File

@ -1886,6 +1886,134 @@ message GetLuckyGiftDrawSummaryResponse {
LuckyGiftDrawSummary summary = 1;
}
message WheelPrizeTier {
string tier_id = 1;
string display_name = 2;
string reward_type = 3;
string reward_id = 4;
int64 reward_count = 5;
int64 reward_coins = 6;
int64 rtp_value_coins = 7;
int64 weight_ppm = 8;
bool enabled = 9;
string metadata_json = 10;
}
message WheelRuleConfig {
string app_code = 1;
string wheel_id = 2;
int64 rule_version = 3;
bool enabled = 4;
int64 draw_price_coins = 5;
int64 target_rtp_ppm = 6;
int64 pool_rate_ppm = 7;
int64 settlement_window_draws = 8;
int64 initial_pool_coins = 9;
int64 pool_reserve_coins = 10;
int64 max_single_rtp_payout = 11;
int64 effective_from_ms = 12;
int64 created_by_admin_id = 13;
int64 created_at_ms = 14;
repeated WheelPrizeTier tiers = 15;
}
message WheelDrawMeta {
RequestMeta meta = 1;
string command_id = 2;
string wheel_id = 3;
int64 user_id = 4;
string device_id = 5;
int32 draw_count = 6;
int64 coin_spent = 7;
int64 paid_at_ms = 8;
int64 visible_region_id = 9;
}
message WheelDrawResult {
string draw_id = 1;
repeated string draw_ids = 2;
string command_id = 3;
string wheel_id = 4;
int64 rule_version = 5;
string selected_tier_id = 6;
string reward_type = 7;
string reward_id = 8;
int64 reward_count = 9;
int64 reward_coins = 10;
int64 rtp_value_coins = 11;
string reward_status = 12;
int64 rtp_window_index = 13;
int64 actual_rtp_ppm = 14;
int64 created_at_ms = 15;
string wallet_transaction_id = 16;
int64 coin_balance_after = 17;
string metadata_json = 18;
}
message ExecuteWheelDrawRequest {
WheelDrawMeta wheel = 1;
}
message ExecuteWheelDrawResponse {
WheelDrawResult result = 1;
}
message GetWheelConfigRequest {
RequestMeta meta = 1;
string wheel_id = 2;
}
message GetWheelConfigResponse {
WheelRuleConfig config = 1;
}
message UpsertWheelConfigRequest {
RequestMeta meta = 1;
WheelRuleConfig config = 2;
int64 operator_admin_id = 3;
}
message UpsertWheelConfigResponse {
WheelRuleConfig config = 1;
}
message ListWheelDrawsRequest {
RequestMeta meta = 1;
string wheel_id = 2;
int64 user_id = 3;
string status = 4;
int32 page = 5;
int32 page_size = 6;
}
message ListWheelDrawsResponse {
repeated WheelDrawResult draws = 1;
int64 total = 2;
}
message WheelDrawSummary {
string wheel_id = 1;
int64 total_draws = 2;
int64 unique_users = 3;
int64 total_spent_coins = 4;
int64 total_rtp_value_coins = 5;
int64 actual_rtp_ppm = 6;
int64 pending_draws = 7;
int64 granted_draws = 8;
int64 failed_draws = 9;
}
message GetWheelDrawSummaryRequest {
RequestMeta meta = 1;
string wheel_id = 2;
int64 user_id = 3;
string status = 4;
}
message GetWheelDrawSummaryResponse {
WheelDrawSummary summary = 1;
}
// WeeklyStarGift
message WeeklyStarGift {
string gift_id = 1;
@ -2191,6 +2319,10 @@ message AgencyOpeningApplication {
int64 granted_at_ms = 16;
int64 updated_at_ms = 17;
int64 reward_threshold_coin_spent = 18;
int64 approved_at_ms = 19;
int64 score_start_ms = 20;
int64 score_end_ms = 21;
int64 reviewed_by_admin_id = 22;
}
// AgencyOpeningAgencySnapshot 使
@ -2263,6 +2395,17 @@ message ListAgencyOpeningApplicationsResponse {
int64 total = 2;
}
message ReviewAgencyOpeningApplicationRequest {
RequestMeta meta = 1;
string application_id = 2;
string action = 3;
int64 operator_admin_id = 4;
}
message ReviewAgencyOpeningApplicationResponse {
AgencyOpeningApplication application = 1;
}
message GetAgencyOpeningStatusRequest {
RequestMeta meta = 1;
int64 user_id = 2;
@ -2350,6 +2493,11 @@ service LuckyGiftService {
rpc ExecuteLuckyGiftDraw(ExecuteLuckyGiftDrawRequest) returns (ExecuteLuckyGiftDrawResponse);
}
// WheelService owns wheel draw decisions after wallet debit succeeds.
service WheelService {
rpc ExecuteWheelDraw(ExecuteWheelDrawRequest) returns (ExecuteWheelDrawResponse);
}
// RoomTurnoverRewardService owns App reads for the weekly room turnover reward activity.
service RoomTurnoverRewardService {
rpc GetRoomTurnoverRewardStatus(GetRoomTurnoverRewardStatusRequest) returns (GetRoomTurnoverRewardStatusResponse);
@ -2483,6 +2631,7 @@ service AdminAgencyOpeningService {
rpc UpdateAgencyOpeningCycle(UpsertAgencyOpeningCycleRequest) returns (UpsertAgencyOpeningCycleResponse);
rpc SetAgencyOpeningCycleStatus(SetAgencyOpeningCycleStatusRequest) returns (SetAgencyOpeningCycleStatusResponse);
rpc ListAgencyOpeningApplications(ListAgencyOpeningApplicationsRequest) returns (ListAgencyOpeningApplicationsResponse);
rpc ReviewAgencyOpeningApplication(ReviewAgencyOpeningApplicationRequest) returns (ReviewAgencyOpeningApplicationResponse);
}
// SevenDayCheckInService App
@ -2520,3 +2669,11 @@ service AdminLuckyGiftService {
rpc ListLuckyGiftDraws(ListLuckyGiftDrawsRequest) returns (ListLuckyGiftDrawsResponse);
rpc GetLuckyGiftDrawSummary(GetLuckyGiftDrawSummaryRequest) returns (GetLuckyGiftDrawSummaryResponse);
}
// AdminWheelService is the admin entry for wheel rule versions, draw records, and RTP statistics.
service AdminWheelService {
rpc GetWheelConfig(GetWheelConfigRequest) returns (GetWheelConfigResponse);
rpc UpsertWheelConfig(UpsertWheelConfigRequest) returns (UpsertWheelConfigResponse);
rpc ListWheelDraws(ListWheelDrawsRequest) returns (ListWheelDrawsResponse);
rpc GetWheelDrawSummary(GetWheelDrawSummaryRequest) returns (GetWheelDrawSummaryResponse);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -170,6 +170,8 @@ message RoomGiftSent {
bool is_robot_gift = 20;
// synthetic_lucky_gift
bool synthetic_lucky_gift = 21;
// real_room_robot_gift Databi
bool real_room_robot_gift = 22;
}
// RoomRobotLuckyGiftDrawn

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc-gen-go-grpc v1.5.1
// - protoc v7.35.0
// source: proto/game/v1/game.proto
@ -271,55 +271,55 @@ type GameAppServiceServer interface {
type UnimplementedGameAppServiceServer struct{}
func (UnimplementedGameAppServiceServer) ListGames(context.Context, *ListGamesRequest) (*ListGamesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListGames not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListGames not implemented")
}
func (UnimplementedGameAppServiceServer) ListRecentGames(context.Context, *ListRecentGamesRequest) (*ListGamesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRecentGames not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRecentGames not implemented")
}
func (UnimplementedGameAppServiceServer) ListExploreWinners(context.Context, *ListExploreWinnersRequest) (*ListExploreWinnersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListExploreWinners not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListExploreWinners not implemented")
}
func (UnimplementedGameAppServiceServer) GetBridgeScript(context.Context, *GetBridgeScriptRequest) (*GetBridgeScriptResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetBridgeScript not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetBridgeScript not implemented")
}
func (UnimplementedGameAppServiceServer) LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error) {
return nil, status.Error(codes.Unimplemented, "method LaunchGame not implemented")
return nil, status.Errorf(codes.Unimplemented, "method LaunchGame not implemented")
}
func (UnimplementedGameAppServiceServer) GetDiceConfig(context.Context, *GetDiceConfigRequest) (*DiceConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetDiceConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetDiceConfig not implemented")
}
func (UnimplementedGameAppServiceServer) MatchDice(context.Context, *MatchDiceRequest) (*DiceMatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method MatchDice not implemented")
return nil, status.Errorf(codes.Unimplemented, "method MatchDice not implemented")
}
func (UnimplementedGameAppServiceServer) CreateDiceMatch(context.Context, *CreateDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateDiceMatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateDiceMatch not implemented")
}
func (UnimplementedGameAppServiceServer) JoinDiceMatch(context.Context, *JoinDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method JoinDiceMatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method JoinDiceMatch not implemented")
}
func (UnimplementedGameAppServiceServer) GetDiceMatch(context.Context, *GetDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetDiceMatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetDiceMatch not implemented")
}
func (UnimplementedGameAppServiceServer) RollDiceMatch(context.Context, *RollDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RollDiceMatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RollDiceMatch not implemented")
}
func (UnimplementedGameAppServiceServer) CancelDiceMatch(context.Context, *CancelDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CancelDiceMatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CancelDiceMatch not implemented")
}
func (UnimplementedGameAppServiceServer) GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRoomRPSConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRPSConfig not implemented")
}
func (UnimplementedGameAppServiceServer) ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRoomRPSChallenges not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRoomRPSChallenges not implemented")
}
func (UnimplementedGameAppServiceServer) CreateRoomRPSChallenge(context.Context, *CreateRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateRoomRPSChallenge not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateRoomRPSChallenge not implemented")
}
func (UnimplementedGameAppServiceServer) AcceptRoomRPSChallenge(context.Context, *AcceptRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AcceptRoomRPSChallenge not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AcceptRoomRPSChallenge not implemented")
}
func (UnimplementedGameAppServiceServer) GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRoomRPSChallenge not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRPSChallenge not implemented")
}
func (UnimplementedGameAppServiceServer) mustEmbedUnimplementedGameAppServiceServer() {}
func (UnimplementedGameAppServiceServer) testEmbeddedByValue() {}
@ -332,7 +332,7 @@ type UnsafeGameAppServiceServer interface {
}
func RegisterGameAppServiceServer(s grpc.ServiceRegistrar, srv GameAppServiceServer) {
// If the following call panics, it indicates UnimplementedGameAppServiceServer was
// If the following call pancis, it indicates UnimplementedGameAppServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -773,7 +773,7 @@ type GameCallbackServiceServer interface {
type UnimplementedGameCallbackServiceServer struct{}
func (UnimplementedGameCallbackServiceServer) HandleCallback(context.Context, *CallbackRequest) (*CallbackResponse, error) {
return nil, status.Error(codes.Unimplemented, "method HandleCallback not implemented")
return nil, status.Errorf(codes.Unimplemented, "method HandleCallback not implemented")
}
func (UnimplementedGameCallbackServiceServer) mustEmbedUnimplementedGameCallbackServiceServer() {}
func (UnimplementedGameCallbackServiceServer) testEmbeddedByValue() {}
@ -786,7 +786,7 @@ type UnsafeGameCallbackServiceServer interface {
}
func RegisterGameCallbackServiceServer(s grpc.ServiceRegistrar, srv GameCallbackServiceServer) {
// If the following call panics, it indicates UnimplementedGameCallbackServiceServer was
// If the following call pancis, it indicates UnimplementedGameCallbackServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -879,7 +879,7 @@ type GameCronServiceServer interface {
type UnimplementedGameCronServiceServer struct{}
func (UnimplementedGameCronServiceServer) ProcessLevelEventOutboxBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ProcessLevelEventOutboxBatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ProcessLevelEventOutboxBatch not implemented")
}
func (UnimplementedGameCronServiceServer) mustEmbedUnimplementedGameCronServiceServer() {}
func (UnimplementedGameCronServiceServer) testEmbeddedByValue() {}
@ -892,7 +892,7 @@ type UnsafeGameCronServiceServer interface {
}
func RegisterGameCronServiceServer(s grpc.ServiceRegistrar, srv GameCronServiceServer) {
// If the following call panics, it indicates UnimplementedGameCronServiceServer was
// If the following call pancis, it indicates UnimplementedGameCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1267,73 +1267,73 @@ type GameAdminServiceServer interface {
type UnimplementedGameAdminServiceServer struct{}
func (UnimplementedGameAdminServiceServer) ListPlatforms(context.Context, *ListPlatformsRequest) (*ListPlatformsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListPlatforms not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListPlatforms not implemented")
}
func (UnimplementedGameAdminServiceServer) UpsertPlatform(context.Context, *UpsertPlatformRequest) (*PlatformResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpsertPlatform not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpsertPlatform not implemented")
}
func (UnimplementedGameAdminServiceServer) ListCatalog(context.Context, *ListCatalogRequest) (*ListCatalogResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListCatalog not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListCatalog not implemented")
}
func (UnimplementedGameAdminServiceServer) UpsertCatalog(context.Context, *UpsertCatalogRequest) (*CatalogResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpsertCatalog not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpsertCatalog not implemented")
}
func (UnimplementedGameAdminServiceServer) SetGameStatus(context.Context, *SetGameStatusRequest) (*CatalogResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetGameStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetGameStatus not implemented")
}
func (UnimplementedGameAdminServiceServer) DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteCatalog not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DeleteCatalog not implemented")
}
func (UnimplementedGameAdminServiceServer) ListSelfGames(context.Context, *ListSelfGamesRequest) (*ListSelfGamesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListSelfGames not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListSelfGames not implemented")
}
func (UnimplementedGameAdminServiceServer) UpdateDiceConfig(context.Context, *UpdateDiceConfigRequest) (*DiceConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateDiceConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateDiceConfig not implemented")
}
func (UnimplementedGameAdminServiceServer) GetSelfGameNewUserPolicy(context.Context, *GetSelfGameNewUserPolicyRequest) (*SelfGameNewUserPolicyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetSelfGameNewUserPolicy not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetSelfGameNewUserPolicy not implemented")
}
func (UnimplementedGameAdminServiceServer) UpdateSelfGameNewUserPolicy(context.Context, *UpdateSelfGameNewUserPolicyRequest) (*SelfGameNewUserPolicyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateSelfGameNewUserPolicy not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateSelfGameNewUserPolicy not implemented")
}
func (UnimplementedGameAdminServiceServer) ListSelfGameStakePools(context.Context, *ListSelfGameStakePoolsRequest) (*ListSelfGameStakePoolsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListSelfGameStakePools not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListSelfGameStakePools not implemented")
}
func (UnimplementedGameAdminServiceServer) UpdateSelfGameStakePool(context.Context, *UpdateSelfGameStakePoolRequest) (*SelfGameStakePoolResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateSelfGameStakePool not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateSelfGameStakePool not implemented")
}
func (UnimplementedGameAdminServiceServer) AdjustSelfGameStakePool(context.Context, *AdjustSelfGameStakePoolRequest) (*AdjustSelfGameStakePoolResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdjustSelfGameStakePool not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdjustSelfGameStakePool not implemented")
}
func (UnimplementedGameAdminServiceServer) ListDiceRobots(context.Context, *ListDiceRobotsRequest) (*ListDiceRobotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListDiceRobots not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListDiceRobots not implemented")
}
func (UnimplementedGameAdminServiceServer) RegisterDiceRobots(context.Context, *RegisterDiceRobotsRequest) (*RegisterDiceRobotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RegisterDiceRobots not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RegisterDiceRobots not implemented")
}
func (UnimplementedGameAdminServiceServer) SetDiceRobotStatus(context.Context, *SetDiceRobotStatusRequest) (*DiceRobotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetDiceRobotStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetDiceRobotStatus not implemented")
}
func (UnimplementedGameAdminServiceServer) DeleteDiceRobot(context.Context, *DeleteDiceRobotRequest) (*DeleteDiceRobotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteDiceRobot not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DeleteDiceRobot not implemented")
}
func (UnimplementedGameAdminServiceServer) GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRoomRPSConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRPSConfig not implemented")
}
func (UnimplementedGameAdminServiceServer) UpdateRoomRPSConfig(context.Context, *UpdateRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateRoomRPSConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateRoomRPSConfig not implemented")
}
func (UnimplementedGameAdminServiceServer) ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRoomRPSChallenges not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRoomRPSChallenges not implemented")
}
func (UnimplementedGameAdminServiceServer) GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRoomRPSChallenge not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRPSChallenge not implemented")
}
func (UnimplementedGameAdminServiceServer) RetryRoomRPSSettlement(context.Context, *RetryRoomRPSSettlementRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RetryRoomRPSSettlement not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RetryRoomRPSSettlement not implemented")
}
func (UnimplementedGameAdminServiceServer) ExpireRoomRPSChallenge(context.Context, *ExpireRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ExpireRoomRPSChallenge not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ExpireRoomRPSChallenge not implemented")
}
func (UnimplementedGameAdminServiceServer) mustEmbedUnimplementedGameAdminServiceServer() {}
func (UnimplementedGameAdminServiceServer) testEmbeddedByValue() {}
@ -1346,7 +1346,7 @@ type UnsafeGameAdminServiceServer interface {
}
func RegisterGameAdminServiceServer(s grpc.ServiceRegistrar, srv GameAdminServiceServer) {
// If the following call panics, it indicates UnimplementedGameAdminServiceServer was
// If the following call pancis, it indicates UnimplementedGameAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc-gen-go v1.35.1
// protoc v7.35.0
// source: proto/robot/v1/robot.proto
@ -11,7 +11,6 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@ -23,14 +22,15 @@ const (
// RequestMeta 是 robot-service 内部 RPC 的租户和追踪上下文;不要把 app_code 放到业务字段里绕过统一鉴权链路。
type RequestMeta struct {
state protoimpl.MessageState `protogen:"open.v1"`
RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
Caller string `protobuf:"bytes,2,opt,name=caller,proto3" json:"caller,omitempty"`
ActorUserId int64 `protobuf:"varint,3,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"`
SentAtMs int64 `protobuf:"varint,4,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"`
AppCode string `protobuf:"bytes,5,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
Caller string `protobuf:"bytes,2,opt,name=caller,proto3" json:"caller,omitempty"`
ActorUserId int64 `protobuf:"varint,3,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"`
SentAtMs int64 `protobuf:"varint,4,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"`
AppCode string `protobuf:"bytes,5,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
}
func (x *RequestMeta) Reset() {
@ -99,20 +99,21 @@ func (x *RequestMeta) GetAppCode() string {
}
type Robot struct {
state protoimpl.MessageState `protogen:"open.v1"`
AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
RobotScope string `protobuf:"bytes,2,opt,name=robot_scope,json=robotScope,proto3" json:"robot_scope,omitempty"`
GameId string `protobuf:"bytes,3,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
RoomScene string `protobuf:"bytes,4,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserId int64 `protobuf:"varint,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"`
CreatedByAdminId int64 `protobuf:"varint,7,opt,name=created_by_admin_id,json=createdByAdminId,proto3" json:"created_by_admin_id,omitempty"`
CreatedAtMs int64 `protobuf:"varint,8,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"`
UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
LastUsedAtMs int64 `protobuf:"varint,10,opt,name=last_used_at_ms,json=lastUsedAtMs,proto3" json:"last_used_at_ms,omitempty"`
UsedCount int64 `protobuf:"varint,11,opt,name=used_count,json=usedCount,proto3" json:"used_count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
RobotScope string `protobuf:"bytes,2,opt,name=robot_scope,json=robotScope,proto3" json:"robot_scope,omitempty"`
GameId string `protobuf:"bytes,3,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
RoomScene string `protobuf:"bytes,4,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserId int64 `protobuf:"varint,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"`
CreatedByAdminId int64 `protobuf:"varint,7,opt,name=created_by_admin_id,json=createdByAdminId,proto3" json:"created_by_admin_id,omitempty"`
CreatedAtMs int64 `protobuf:"varint,8,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"`
UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
LastUsedAtMs int64 `protobuf:"varint,10,opt,name=last_used_at_ms,json=lastUsedAtMs,proto3" json:"last_used_at_ms,omitempty"`
UsedCount int64 `protobuf:"varint,11,opt,name=used_count,json=usedCount,proto3" json:"used_count,omitempty"`
}
func (x *Robot) Reset() {
@ -223,14 +224,15 @@ func (x *Robot) GetUsedCount() int64 {
}
type ListGameRobotsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
}
func (x *ListGameRobotsRequest) Reset() {
@ -299,12 +301,13 @@ func (x *ListGameRobotsRequest) GetCursor() string {
}
type ListGameRobotsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
}
func (x *ListGameRobotsResponse) Reset() {
@ -359,12 +362,13 @@ func (x *ListGameRobotsResponse) GetServerTimeMs() int64 {
}
type RegisterGameRobotsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
}
func (x *RegisterGameRobotsRequest) Reset() {
@ -419,11 +423,12 @@ func (x *RegisterGameRobotsRequest) GetUserIds() []int64 {
}
type RegisterGameRobotsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
}
func (x *RegisterGameRobotsResponse) Reset() {
@ -471,14 +476,15 @@ func (x *RegisterGameRobotsResponse) GetServerTimeMs() int64 {
}
type PickGameRobotRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"`
SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"`
ExcludeUserIds []int64 `protobuf:"varint,5,rep,packed,name=exclude_user_ids,json=excludeUserIds,proto3" json:"exclude_user_ids,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"`
SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"`
ExcludeUserIds []int64 `protobuf:"varint,5,rep,packed,name=exclude_user_ids,json=excludeUserIds,proto3" json:"exclude_user_ids,omitempty"`
}
func (x *PickGameRobotRequest) Reset() {
@ -547,11 +553,12 @@ func (x *PickGameRobotRequest) GetExcludeUserIds() []int64 {
}
type GameRobotResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Robot *Robot `protobuf:"bytes,1,opt,name=robot,proto3" json:"robot,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robot *Robot `protobuf:"bytes,1,opt,name=robot,proto3" json:"robot,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
}
func (x *GameRobotResponse) Reset() {
@ -599,13 +606,14 @@ func (x *GameRobotResponse) GetServerTimeMs() int64 {
}
type SetGameRobotStatusRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *SetGameRobotStatusRequest) Reset() {
@ -667,12 +675,13 @@ func (x *SetGameRobotStatusRequest) GetStatus() string {
}
type DeleteGameRobotRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
}
func (x *DeleteGameRobotRequest) Reset() {
@ -727,11 +736,12 @@ func (x *DeleteGameRobotRequest) GetUserId() int64 {
}
type DeleteGameRobotResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
}
func (x *DeleteGameRobotResponse) Reset() {
@ -779,14 +789,15 @@ func (x *DeleteGameRobotResponse) GetServerTimeMs() int64 {
}
type ListRoomRobotsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
}
func (x *ListRoomRobotsRequest) Reset() {
@ -855,12 +866,13 @@ func (x *ListRoomRobotsRequest) GetCursor() string {
}
type ListRoomRobotsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
}
func (x *ListRoomRobotsResponse) Reset() {
@ -915,12 +927,13 @@ func (x *ListRoomRobotsResponse) GetServerTimeMs() int64 {
}
type RegisterRoomRobotsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
}
func (x *RegisterRoomRobotsRequest) Reset() {
@ -975,11 +988,12 @@ func (x *RegisterRoomRobotsRequest) GetUserIds() []int64 {
}
type RegisterRoomRobotsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
}
func (x *RegisterRoomRobotsResponse) Reset() {
@ -1027,13 +1041,14 @@ func (x *RegisterRoomRobotsResponse) GetServerTimeMs() int64 {
}
type SetRoomRobotStatusRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *SetRoomRobotStatusRequest) Reset() {
@ -1095,12 +1110,13 @@ func (x *SetRoomRobotStatusRequest) GetStatus() string {
}
type DeleteRoomRobotRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
}
func (x *DeleteRoomRobotRequest) Reset() {
@ -1155,11 +1171,12 @@ func (x *DeleteRoomRobotRequest) GetUserId() int64 {
}
type DeleteRoomRobotResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
}
func (x *DeleteRoomRobotResponse) Reset() {
@ -1207,13 +1224,14 @@ func (x *DeleteRoomRobotResponse) GetServerTimeMs() int64 {
}
type PickRoomRobotRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"`
}
func (x *PickRoomRobotRequest) Reset() {
@ -1275,11 +1293,12 @@ func (x *PickRoomRobotRequest) GetSelectedAtMs() int64 {
}
type RoomRobotResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Robot *Robot `protobuf:"bytes,1,opt,name=robot,proto3" json:"robot,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robot *Robot `protobuf:"bytes,1,opt,name=robot,proto3" json:"robot,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
}
func (x *RoomRobotResponse) Reset() {
@ -1328,136 +1347,276 @@ func (x *RoomRobotResponse) GetServerTimeMs() int64 {
var File_proto_robot_v1_robot_proto protoreflect.FileDescriptor
const file_proto_robot_v1_robot_proto_rawDesc = "" +
"\n" +
"\x1aproto/robot/v1/robot.proto\x12\x0ehyapp.robot.v1\"\xa1\x01\n" +
"\vRequestMeta\x12\x1d\n" +
"\n" +
"request_id\x18\x01 \x01(\tR\trequestId\x12\x16\n" +
"\x06caller\x18\x02 \x01(\tR\x06caller\x12\"\n" +
"\ractor_user_id\x18\x03 \x01(\x03R\vactorUserId\x12\x1c\n" +
"\n" +
"sent_at_ms\x18\x04 \x01(\x03R\bsentAtMs\x12\x19\n" +
"\bapp_code\x18\x05 \x01(\tR\aappCode\"\xe9\x02\n" +
"\x05Robot\x12\x19\n" +
"\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x1f\n" +
"\vrobot_scope\x18\x02 \x01(\tR\n" +
"robotScope\x12\x17\n" +
"\agame_id\x18\x03 \x01(\tR\x06gameId\x12\x1d\n" +
"\n" +
"room_scene\x18\x04 \x01(\tR\troomScene\x12\x17\n" +
"\auser_id\x18\x05 \x01(\x03R\x06userId\x12\x16\n" +
"\x06status\x18\x06 \x01(\tR\x06status\x12-\n" +
"\x13created_by_admin_id\x18\a \x01(\x03R\x10createdByAdminId\x12\"\n" +
"\rcreated_at_ms\x18\b \x01(\x03R\vcreatedAtMs\x12\"\n" +
"\rupdated_at_ms\x18\t \x01(\x03R\vupdatedAtMs\x12%\n" +
"\x0flast_used_at_ms\x18\n" +
" \x01(\x03R\flastUsedAtMs\x12\x1d\n" +
"\n" +
"used_count\x18\v \x01(\x03R\tusedCount\"\xae\x01\n" +
"\x15ListGameRobotsRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
"\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x16\n" +
"\x06status\x18\x03 \x01(\tR\x06status\x12\x1b\n" +
"\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12\x16\n" +
"\x06cursor\x18\x05 \x01(\tR\x06cursor\"\x8e\x01\n" +
"\x16ListGameRobotsResponse\x12-\n" +
"\x06robots\x18\x01 \x03(\v2\x15.hyapp.robot.v1.RobotR\x06robots\x12\x1f\n" +
"\vnext_cursor\x18\x02 \x01(\tR\n" +
"nextCursor\x12$\n" +
"\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\x80\x01\n" +
"\x19RegisterGameRobotsRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
"\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x19\n" +
"\buser_ids\x18\x03 \x03(\x03R\auserIds\"q\n" +
"\x1aRegisterGameRobotsResponse\x12-\n" +
"\x06robots\x18\x01 \x03(\v2\x15.hyapp.robot.v1.RobotR\x06robots\x12$\n" +
"\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xcb\x01\n" +
"\x14PickGameRobotRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
"\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x19\n" +
"\bmatch_id\x18\x03 \x01(\tR\amatchId\x12$\n" +
"\x0eselected_at_ms\x18\x04 \x01(\x03R\fselectedAtMs\x12(\n" +
"\x10exclude_user_ids\x18\x05 \x03(\x03R\x0eexcludeUserIds\"f\n" +
"\x11GameRobotResponse\x12+\n" +
"\x05robot\x18\x01 \x01(\v2\x15.hyapp.robot.v1.RobotR\x05robot\x12$\n" +
"\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\x96\x01\n" +
"\x19SetGameRobotStatusRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
"\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x17\n" +
"\auser_id\x18\x03 \x01(\x03R\x06userId\x12\x16\n" +
"\x06status\x18\x04 \x01(\tR\x06status\"{\n" +
"\x16DeleteGameRobotRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
"\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x17\n" +
"\auser_id\x18\x03 \x01(\x03R\x06userId\"Y\n" +
"\x17DeleteGameRobotResponse\x12\x18\n" +
"\adeleted\x18\x01 \x01(\bR\adeleted\x12$\n" +
"\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xb4\x01\n" +
"\x15ListRoomRobotsRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
"\n" +
"room_scene\x18\x02 \x01(\tR\troomScene\x12\x16\n" +
"\x06status\x18\x03 \x01(\tR\x06status\x12\x1b\n" +
"\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12\x16\n" +
"\x06cursor\x18\x05 \x01(\tR\x06cursor\"\x8e\x01\n" +
"\x16ListRoomRobotsResponse\x12-\n" +
"\x06robots\x18\x01 \x03(\v2\x15.hyapp.robot.v1.RobotR\x06robots\x12\x1f\n" +
"\vnext_cursor\x18\x02 \x01(\tR\n" +
"nextCursor\x12$\n" +
"\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\x86\x01\n" +
"\x19RegisterRoomRobotsRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
"\n" +
"room_scene\x18\x02 \x01(\tR\troomScene\x12\x19\n" +
"\buser_ids\x18\x03 \x03(\x03R\auserIds\"q\n" +
"\x1aRegisterRoomRobotsResponse\x12-\n" +
"\x06robots\x18\x01 \x03(\v2\x15.hyapp.robot.v1.RobotR\x06robots\x12$\n" +
"\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\x9c\x01\n" +
"\x19SetRoomRobotStatusRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
"\n" +
"room_scene\x18\x02 \x01(\tR\troomScene\x12\x17\n" +
"\auser_id\x18\x03 \x01(\x03R\x06userId\x12\x16\n" +
"\x06status\x18\x04 \x01(\tR\x06status\"\x81\x01\n" +
"\x16DeleteRoomRobotRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
"\n" +
"room_scene\x18\x02 \x01(\tR\troomScene\x12\x17\n" +
"\auser_id\x18\x03 \x01(\x03R\x06userId\"Y\n" +
"\x17DeleteRoomRobotResponse\x12\x18\n" +
"\adeleted\x18\x01 \x01(\bR\adeleted\x12$\n" +
"\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xa5\x01\n" +
"\x14PickRoomRobotRequest\x12/\n" +
"\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
"\n" +
"room_scene\x18\x02 \x01(\tR\troomScene\x12\x17\n" +
"\aroom_id\x18\x03 \x01(\tR\x06roomId\x12$\n" +
"\x0eselected_at_ms\x18\x04 \x01(\x03R\fselectedAtMs\"f\n" +
"\x11RoomRobotResponse\x12+\n" +
"\x05robot\x18\x01 \x01(\v2\x15.hyapp.robot.v1.RobotR\x05robot\x12$\n" +
"\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs2\x82\x04\n" +
"\x10GameRobotService\x12_\n" +
"\x0eListGameRobots\x12%.hyapp.robot.v1.ListGameRobotsRequest\x1a&.hyapp.robot.v1.ListGameRobotsResponse\x12k\n" +
"\x12RegisterGameRobots\x12).hyapp.robot.v1.RegisterGameRobotsRequest\x1a*.hyapp.robot.v1.RegisterGameRobotsResponse\x12X\n" +
"\rPickGameRobot\x12$.hyapp.robot.v1.PickGameRobotRequest\x1a!.hyapp.robot.v1.GameRobotResponse\x12b\n" +
"\x12SetGameRobotStatus\x12).hyapp.robot.v1.SetGameRobotStatusRequest\x1a!.hyapp.robot.v1.GameRobotResponse\x12b\n" +
"\x0fDeleteGameRobot\x12&.hyapp.robot.v1.DeleteGameRobotRequest\x1a'.hyapp.robot.v1.DeleteGameRobotResponse2\x82\x04\n" +
"\x10RoomRobotService\x12_\n" +
"\x0eListRoomRobots\x12%.hyapp.robot.v1.ListRoomRobotsRequest\x1a&.hyapp.robot.v1.ListRoomRobotsResponse\x12k\n" +
"\x12RegisterRoomRobots\x12).hyapp.robot.v1.RegisterRoomRobotsRequest\x1a*.hyapp.robot.v1.RegisterRoomRobotsResponse\x12X\n" +
"\rPickRoomRobot\x12$.hyapp.robot.v1.PickRoomRobotRequest\x1a!.hyapp.robot.v1.RoomRobotResponse\x12b\n" +
"\x12SetRoomRobotStatus\x12).hyapp.robot.v1.SetRoomRobotStatusRequest\x1a!.hyapp.robot.v1.RoomRobotResponse\x12b\n" +
"\x0fDeleteRoomRobot\x12&.hyapp.robot.v1.DeleteRoomRobotRequest\x1a'.hyapp.robot.v1.DeleteRoomRobotResponseB(Z&hyapp.local/api/proto/robot/v1;robotv1b\x06proto3"
var file_proto_robot_v1_robot_proto_rawDesc = []byte{
0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2f, 0x76, 0x31,
0x2f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x22, 0xa1, 0x01, 0x0a,
0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a,
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x63,
0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, 0x6c,
0x6c, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f,
0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x65, 0x6e, 0x74, 0x5f,
0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x65, 0x6e,
0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64,
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65,
0x22, 0xe9, 0x02, 0x0a, 0x05, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70,
0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70,
0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x73,
0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x62, 0x6f,
0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69,
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12,
0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x17,
0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52,
0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x2d, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64,
0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x72,
0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22,
0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18,
0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74,
0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74,
0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x25, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75,
0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0c, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x73, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1d, 0x0a,
0x0a, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28,
0x03, 0x52, 0x09, 0x75, 0x73, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xae, 0x01, 0x0a,
0x15, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62,
0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74,
0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64,
0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65,
0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67,
0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x22, 0x8e, 0x01,
0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x6f, 0x62, 0x6f,
0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52,
0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x65, 0x78, 0x74, 0x5f,
0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x65,
0x78, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76,
0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x80,
0x01, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x47, 0x61, 0x6d, 0x65, 0x52,
0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04,
0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a,
0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64,
0x73, 0x22, 0x71, 0x0a, 0x1a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x47, 0x61, 0x6d,
0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x2d, 0x0a, 0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x24,
0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69,
0x6d, 0x65, 0x4d, 0x73, 0x22, 0xcb, 0x01, 0x0a, 0x14, 0x50, 0x69, 0x63, 0x6b, 0x47, 0x61, 0x6d,
0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a,
0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17,
0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x61, 0x74, 0x63, 0x68,
0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68,
0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61,
0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x65,
0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x63, 0x6c,
0x75, 0x64, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03,
0x28, 0x03, 0x52, 0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49,
0x64, 0x73, 0x22, 0x66, 0x0a, 0x11, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x72, 0x6f, 0x62, 0x6f, 0x74,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72,
0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x05, 0x72,
0x6f, 0x62, 0x6f, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74,
0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x19, 0x53,
0x65, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72,
0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d,
0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x6d,
0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65,
0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x22, 0x7b, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x6d,
0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a,
0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17,
0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f,
0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64,
0x22, 0x59, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f,
0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x15,
0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f,
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61,
0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73,
0x63, 0x65, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x6d,
0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a,
0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05,
0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75,
0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73,
0x6f, 0x72, 0x22, 0x8e, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52,
0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a,
0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52,
0x6f, 0x62, 0x6f, 0x74, 0x52, 0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b,
0x6e, 0x65, 0x78, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x24, 0x0a,
0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18,
0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d,
0x65, 0x4d, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65,
0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x65, 0x6e, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x65, 0x6e,
0x65, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0x71, 0x0a, 0x1a,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x6f,
0x62, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x52, 0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22,
0x9c, 0x01, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a,
0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d,
0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x17, 0x0a,
0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x81,
0x01, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62,
0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74,
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f,
0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72,
0x49, 0x64, 0x22, 0x59, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d,
0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a,
0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xa5, 0x01,
0x0a, 0x14, 0x50, 0x69, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62,
0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74,
0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f,
0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f,
0x6d, 0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69,
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12,
0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d,
0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65,
0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x66, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62,
0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x72, 0x6f,
0x62, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x62, 0x6f, 0x74,
0x52, 0x05, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x32, 0x82, 0x04,
0x0a, 0x10, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f,
0x62, 0x6f, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62,
0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f,
0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x47,
0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62,
0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x47, 0x61,
0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x58, 0x0a, 0x0d, 0x50, 0x69, 0x63, 0x6b, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e,
0x76, 0x31, 0x2e, 0x50, 0x69, 0x63, 0x6b, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62,
0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x12, 0x53, 0x65,
0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76,
0x31, 0x2e, 0x53, 0x65, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x61, 0x6d,
0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62,
0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e,
0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62,
0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x32, 0x82, 0x04, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x52,
0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52,
0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76,
0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69,
0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x29,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0d, 0x50, 0x69, 0x63, 0x6b, 0x52, 0x6f, 0x6f,
0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72,
0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x6d,
0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f,
0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x62, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f,
0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76,
0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f,
0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72,
0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f,
0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x28, 0x5a, 0x26, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x76,
0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proto_robot_v1_robot_proto_rawDescOnce sync.Once
file_proto_robot_v1_robot_proto_rawDescData []byte
file_proto_robot_v1_robot_proto_rawDescData = file_proto_robot_v1_robot_proto_rawDesc
)
func file_proto_robot_v1_robot_proto_rawDescGZIP() []byte {
file_proto_robot_v1_robot_proto_rawDescOnce.Do(func() {
file_proto_robot_v1_robot_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_robot_v1_robot_proto_rawDesc), len(file_proto_robot_v1_robot_proto_rawDesc)))
file_proto_robot_v1_robot_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_robot_v1_robot_proto_rawDescData)
})
return file_proto_robot_v1_robot_proto_rawDescData
}
@ -1538,7 +1697,7 @@ func file_proto_robot_v1_robot_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_robot_v1_robot_proto_rawDesc), len(file_proto_robot_v1_robot_proto_rawDesc)),
RawDescriptor: file_proto_robot_v1_robot_proto_rawDesc,
NumEnums: 0,
NumMessages: 20,
NumExtensions: 0,
@ -1549,6 +1708,7 @@ func file_proto_robot_v1_robot_proto_init() {
MessageInfos: file_proto_robot_v1_robot_proto_msgTypes,
}.Build()
File_proto_robot_v1_robot_proto = out.File
file_proto_robot_v1_robot_proto_rawDesc = nil
file_proto_robot_v1_robot_proto_goTypes = nil
file_proto_robot_v1_robot_proto_depIdxs = nil
}

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc-gen-go-grpc v1.5.1
// - protoc v7.35.0
// source: proto/robot/v1/robot.proto
@ -115,19 +115,19 @@ type GameRobotServiceServer interface {
type UnimplementedGameRobotServiceServer struct{}
func (UnimplementedGameRobotServiceServer) ListGameRobots(context.Context, *ListGameRobotsRequest) (*ListGameRobotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListGameRobots not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListGameRobots not implemented")
}
func (UnimplementedGameRobotServiceServer) RegisterGameRobots(context.Context, *RegisterGameRobotsRequest) (*RegisterGameRobotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RegisterGameRobots not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RegisterGameRobots not implemented")
}
func (UnimplementedGameRobotServiceServer) PickGameRobot(context.Context, *PickGameRobotRequest) (*GameRobotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method PickGameRobot not implemented")
return nil, status.Errorf(codes.Unimplemented, "method PickGameRobot not implemented")
}
func (UnimplementedGameRobotServiceServer) SetGameRobotStatus(context.Context, *SetGameRobotStatusRequest) (*GameRobotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetGameRobotStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetGameRobotStatus not implemented")
}
func (UnimplementedGameRobotServiceServer) DeleteGameRobot(context.Context, *DeleteGameRobotRequest) (*DeleteGameRobotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteGameRobot not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DeleteGameRobot not implemented")
}
func (UnimplementedGameRobotServiceServer) mustEmbedUnimplementedGameRobotServiceServer() {}
func (UnimplementedGameRobotServiceServer) testEmbeddedByValue() {}
@ -140,7 +140,7 @@ type UnsafeGameRobotServiceServer interface {
}
func RegisterGameRobotServiceServer(s grpc.ServiceRegistrar, srv GameRobotServiceServer) {
// If the following call panics, it indicates UnimplementedGameRobotServiceServer was
// If the following call pancis, it indicates UnimplementedGameRobotServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -369,19 +369,19 @@ type RoomRobotServiceServer interface {
type UnimplementedRoomRobotServiceServer struct{}
func (UnimplementedRoomRobotServiceServer) ListRoomRobots(context.Context, *ListRoomRobotsRequest) (*ListRoomRobotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRoomRobots not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRoomRobots not implemented")
}
func (UnimplementedRoomRobotServiceServer) RegisterRoomRobots(context.Context, *RegisterRoomRobotsRequest) (*RegisterRoomRobotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RegisterRoomRobots not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RegisterRoomRobots not implemented")
}
func (UnimplementedRoomRobotServiceServer) PickRoomRobot(context.Context, *PickRoomRobotRequest) (*RoomRobotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method PickRoomRobot not implemented")
return nil, status.Errorf(codes.Unimplemented, "method PickRoomRobot not implemented")
}
func (UnimplementedRoomRobotServiceServer) SetRoomRobotStatus(context.Context, *SetRoomRobotStatusRequest) (*RoomRobotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetRoomRobotStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetRoomRobotStatus not implemented")
}
func (UnimplementedRoomRobotServiceServer) DeleteRoomRobot(context.Context, *DeleteRoomRobotRequest) (*DeleteRoomRobotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteRoomRobot not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DeleteRoomRobot not implemented")
}
func (UnimplementedRoomRobotServiceServer) mustEmbedUnimplementedRoomRobotServiceServer() {}
func (UnimplementedRoomRobotServiceServer) testEmbeddedByValue() {}
@ -394,7 +394,7 @@ type UnsafeRoomRobotServiceServer interface {
}
func RegisterRoomRobotServiceServer(s grpc.ServiceRegistrar, srv RoomRobotServiceServer) {
// If the following call panics, it indicates UnimplementedRoomRobotServiceServer was
// If the following call pancis, it indicates UnimplementedRoomRobotServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -268,6 +268,59 @@ message AdminUpdateRoomSeatConfigResponse {
int64 server_time_ms = 2;
}
message AdminHumanRoomRobotCountryPool {
string country_code = 1;
repeated int64 robot_user_ids = 2;
}
message AdminHumanRoomRobotConfig {
string app_code = 1;
bool enabled = 2;
int32 candidate_room_max_online = 3;
int32 room_full_stop_online = 4;
int64 robot_stay_min_ms = 5;
int64 robot_stay_max_ms = 6;
int64 robot_replace_min_ms = 7;
int64 robot_replace_max_ms = 8;
int64 normal_gift_interval_ms = 9;
repeated string gift_ids = 10;
repeated string lucky_gift_ids = 11;
repeated string super_lucky_gift_ids = 12;
int64 lucky_combo_min = 13;
int64 lucky_combo_max = 14;
int64 lucky_pause_min_ms = 15;
int64 lucky_pause_max_ms = 16;
int64 max_gift_senders = 17;
repeated AdminHumanRoomRobotCountryPool country_pools = 18;
uint64 updated_by_admin_id = 19;
int64 created_at_ms = 20;
int64 updated_at_ms = 21;
int64 normal_gift_interval_min_ms = 22;
int64 normal_gift_interval_max_ms = 23;
int32 room_target_min_online = 24;
int32 room_target_max_online = 25;
}
message AdminGetHumanRoomRobotConfigRequest {
RequestMeta meta = 1;
}
message AdminGetHumanRoomRobotConfigResponse {
AdminHumanRoomRobotConfig config = 1;
int64 server_time_ms = 2;
}
message AdminUpdateHumanRoomRobotConfigRequest {
RequestMeta meta = 1;
AdminHumanRoomRobotConfig config = 2;
uint64 admin_id = 3;
}
message AdminUpdateHumanRoomRobotConfigResponse {
AdminHumanRoomRobotConfig config = 1;
int64 server_time_ms = 2;
}
message AdminRoomPinRoom {
string room_id = 1;
string room_short_id = 2;
@ -346,6 +399,11 @@ message AdminRobotRoomGiftRule {
int64 lucky_combo_max = 5;
int64 lucky_pause_min_ms = 6;
int64 lucky_pause_max_ms = 7;
int64 robot_stay_min_ms = 8;
int64 robot_stay_max_ms = 9;
int64 robot_replace_min_ms = 10;
int64 robot_replace_max_ms = 11;
int64 max_gift_senders = 12;
}
message AdminRobotRoom {
@ -362,6 +420,8 @@ message AdminRobotRoom {
uint64 created_by_admin_id = 11;
int64 created_at_ms = 12;
int64 updated_at_ms = 13;
int32 active_robot_count = 14;
int32 seat_count = 15;
}
message AdminListRobotRoomsRequest {
@ -388,6 +448,8 @@ message AdminCreateRobotRoomRequest {
int64 visible_region_id = 8;
AdminRobotRoomGiftRule gift_rule = 9;
uint64 admin_id = 10;
string owner_country_code = 11;
int32 seat_count = 12;
}
message AdminCreateRobotRoomResponse {
@ -1005,6 +1067,8 @@ message ListRoomsRequest {
string country_code = 8;
// viewer_country_code
string viewer_country_code = 9;
// all_visible_regions gateway true visible_region_id
bool all_visible_regions = 10;
}
// ListRoomFeedsRequest Mine visited/friend/following/followed
@ -1232,6 +1296,7 @@ service RoomCommandService {
rpc AdminDeleteRoom(AdminDeleteRoomRequest) returns (AdminDeleteRoomResponse);
rpc AdminUpdateRoomRocketConfig(AdminUpdateRoomRocketConfigRequest) returns (AdminUpdateRoomRocketConfigResponse);
rpc AdminUpdateRoomSeatConfig(AdminUpdateRoomSeatConfigRequest) returns (AdminUpdateRoomSeatConfigResponse);
rpc AdminUpdateHumanRoomRobotConfig(AdminUpdateHumanRoomRobotConfigRequest) returns (AdminUpdateHumanRoomRobotConfigResponse);
rpc AdminCreateRoomPin(AdminCreateRoomPinRequest) returns (AdminCreateRoomPinResponse);
rpc AdminCancelRoomPin(AdminCancelRoomPinRequest) returns (AdminCancelRoomPinResponse);
rpc AdminCreateRobotRoom(AdminCreateRobotRoomRequest) returns (AdminCreateRobotRoomResponse);
@ -1268,6 +1333,7 @@ service RoomQueryService {
rpc AdminGetRoom(AdminGetRoomRequest) returns (AdminGetRoomResponse);
rpc AdminGetRoomRocketConfig(AdminGetRoomRocketConfigRequest) returns (AdminGetRoomRocketConfigResponse);
rpc AdminGetRoomSeatConfig(AdminGetRoomSeatConfigRequest) returns (AdminGetRoomSeatConfigResponse);
rpc AdminGetHumanRoomRobotConfig(AdminGetHumanRoomRobotConfigRequest) returns (AdminGetHumanRoomRobotConfigResponse);
rpc AdminListRoomPins(AdminListRoomPinsRequest) returns (AdminListRoomPinsResponse);
rpc AdminListRobotRooms(AdminListRobotRoomsRequest) returns (AdminListRobotRoomsResponse);
rpc AdminFilterAvailableRoomRobots(AdminFilterAvailableRoomRobotsRequest) returns (AdminFilterAvailableRoomRobotsResponse);

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc-gen-go-grpc v1.5.1
// - protoc v7.35.0
// source: proto/room/v1/room.proto
@ -19,40 +19,41 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
RoomCommandService_CreateRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/CreateRoom"
RoomCommandService_UpdateRoomProfile_FullMethodName = "/hyapp.room.v1.RoomCommandService/UpdateRoomProfile"
RoomCommandService_SaveRoomBackground_FullMethodName = "/hyapp.room.v1.RoomCommandService/SaveRoomBackground"
RoomCommandService_SetRoomBackground_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetRoomBackground"
RoomCommandService_JoinRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/JoinRoom"
RoomCommandService_RoomHeartbeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/RoomHeartbeat"
RoomCommandService_LeaveRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/LeaveRoom"
RoomCommandService_CloseRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/CloseRoom"
RoomCommandService_AdminUpdateRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminUpdateRoom"
RoomCommandService_AdminDeleteRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminDeleteRoom"
RoomCommandService_AdminUpdateRoomRocketConfig_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminUpdateRoomRocketConfig"
RoomCommandService_AdminUpdateRoomSeatConfig_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminUpdateRoomSeatConfig"
RoomCommandService_AdminCreateRoomPin_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminCreateRoomPin"
RoomCommandService_AdminCancelRoomPin_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminCancelRoomPin"
RoomCommandService_AdminCreateRobotRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminCreateRobotRoom"
RoomCommandService_AdminSetRobotRoomStatus_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminSetRobotRoomStatus"
RoomCommandService_MicUp_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicUp"
RoomCommandService_MicDown_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicDown"
RoomCommandService_ChangeMicSeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/ChangeMicSeat"
RoomCommandService_ConfirmMicPublishing_FullMethodName = "/hyapp.room.v1.RoomCommandService/ConfirmMicPublishing"
RoomCommandService_MicHeartbeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicHeartbeat"
RoomCommandService_SetMicMute_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetMicMute"
RoomCommandService_ApplyRTCEvent_FullMethodName = "/hyapp.room.v1.RoomCommandService/ApplyRTCEvent"
RoomCommandService_SetMicSeatLock_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetMicSeatLock"
RoomCommandService_SetChatEnabled_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetChatEnabled"
RoomCommandService_SetRoomPassword_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetRoomPassword"
RoomCommandService_SetRoomAdmin_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetRoomAdmin"
RoomCommandService_MuteUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/MuteUser"
RoomCommandService_KickUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/KickUser"
RoomCommandService_UnbanUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/UnbanUser"
RoomCommandService_SystemEvictUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/SystemEvictUser"
RoomCommandService_SendGift_FullMethodName = "/hyapp.room.v1.RoomCommandService/SendGift"
RoomCommandService_FollowRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/FollowRoom"
RoomCommandService_UnfollowRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/UnfollowRoom"
RoomCommandService_CreateRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/CreateRoom"
RoomCommandService_UpdateRoomProfile_FullMethodName = "/hyapp.room.v1.RoomCommandService/UpdateRoomProfile"
RoomCommandService_SaveRoomBackground_FullMethodName = "/hyapp.room.v1.RoomCommandService/SaveRoomBackground"
RoomCommandService_SetRoomBackground_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetRoomBackground"
RoomCommandService_JoinRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/JoinRoom"
RoomCommandService_RoomHeartbeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/RoomHeartbeat"
RoomCommandService_LeaveRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/LeaveRoom"
RoomCommandService_CloseRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/CloseRoom"
RoomCommandService_AdminUpdateRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminUpdateRoom"
RoomCommandService_AdminDeleteRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminDeleteRoom"
RoomCommandService_AdminUpdateRoomRocketConfig_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminUpdateRoomRocketConfig"
RoomCommandService_AdminUpdateRoomSeatConfig_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminUpdateRoomSeatConfig"
RoomCommandService_AdminUpdateHumanRoomRobotConfig_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminUpdateHumanRoomRobotConfig"
RoomCommandService_AdminCreateRoomPin_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminCreateRoomPin"
RoomCommandService_AdminCancelRoomPin_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminCancelRoomPin"
RoomCommandService_AdminCreateRobotRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminCreateRobotRoom"
RoomCommandService_AdminSetRobotRoomStatus_FullMethodName = "/hyapp.room.v1.RoomCommandService/AdminSetRobotRoomStatus"
RoomCommandService_MicUp_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicUp"
RoomCommandService_MicDown_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicDown"
RoomCommandService_ChangeMicSeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/ChangeMicSeat"
RoomCommandService_ConfirmMicPublishing_FullMethodName = "/hyapp.room.v1.RoomCommandService/ConfirmMicPublishing"
RoomCommandService_MicHeartbeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicHeartbeat"
RoomCommandService_SetMicMute_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetMicMute"
RoomCommandService_ApplyRTCEvent_FullMethodName = "/hyapp.room.v1.RoomCommandService/ApplyRTCEvent"
RoomCommandService_SetMicSeatLock_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetMicSeatLock"
RoomCommandService_SetChatEnabled_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetChatEnabled"
RoomCommandService_SetRoomPassword_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetRoomPassword"
RoomCommandService_SetRoomAdmin_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetRoomAdmin"
RoomCommandService_MuteUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/MuteUser"
RoomCommandService_KickUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/KickUser"
RoomCommandService_UnbanUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/UnbanUser"
RoomCommandService_SystemEvictUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/SystemEvictUser"
RoomCommandService_SendGift_FullMethodName = "/hyapp.room.v1.RoomCommandService/SendGift"
RoomCommandService_FollowRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/FollowRoom"
RoomCommandService_UnfollowRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/UnfollowRoom"
)
// RoomCommandServiceClient is the client API for RoomCommandService service.
@ -73,6 +74,7 @@ type RoomCommandServiceClient interface {
AdminDeleteRoom(ctx context.Context, in *AdminDeleteRoomRequest, opts ...grpc.CallOption) (*AdminDeleteRoomResponse, error)
AdminUpdateRoomRocketConfig(ctx context.Context, in *AdminUpdateRoomRocketConfigRequest, opts ...grpc.CallOption) (*AdminUpdateRoomRocketConfigResponse, error)
AdminUpdateRoomSeatConfig(ctx context.Context, in *AdminUpdateRoomSeatConfigRequest, opts ...grpc.CallOption) (*AdminUpdateRoomSeatConfigResponse, error)
AdminUpdateHumanRoomRobotConfig(ctx context.Context, in *AdminUpdateHumanRoomRobotConfigRequest, opts ...grpc.CallOption) (*AdminUpdateHumanRoomRobotConfigResponse, error)
AdminCreateRoomPin(ctx context.Context, in *AdminCreateRoomPinRequest, opts ...grpc.CallOption) (*AdminCreateRoomPinResponse, error)
AdminCancelRoomPin(ctx context.Context, in *AdminCancelRoomPinRequest, opts ...grpc.CallOption) (*AdminCancelRoomPinResponse, error)
AdminCreateRobotRoom(ctx context.Context, in *AdminCreateRobotRoomRequest, opts ...grpc.CallOption) (*AdminCreateRobotRoomResponse, error)
@ -225,6 +227,16 @@ func (c *roomCommandServiceClient) AdminUpdateRoomSeatConfig(ctx context.Context
return out, nil
}
func (c *roomCommandServiceClient) AdminUpdateHumanRoomRobotConfig(ctx context.Context, in *AdminUpdateHumanRoomRobotConfigRequest, opts ...grpc.CallOption) (*AdminUpdateHumanRoomRobotConfigResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AdminUpdateHumanRoomRobotConfigResponse)
err := c.cc.Invoke(ctx, RoomCommandService_AdminUpdateHumanRoomRobotConfig_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roomCommandServiceClient) AdminCreateRoomPin(ctx context.Context, in *AdminCreateRoomPinRequest, opts ...grpc.CallOption) (*AdminCreateRoomPinResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AdminCreateRoomPinResponse)
@ -463,6 +475,7 @@ type RoomCommandServiceServer interface {
AdminDeleteRoom(context.Context, *AdminDeleteRoomRequest) (*AdminDeleteRoomResponse, error)
AdminUpdateRoomRocketConfig(context.Context, *AdminUpdateRoomRocketConfigRequest) (*AdminUpdateRoomRocketConfigResponse, error)
AdminUpdateRoomSeatConfig(context.Context, *AdminUpdateRoomSeatConfigRequest) (*AdminUpdateRoomSeatConfigResponse, error)
AdminUpdateHumanRoomRobotConfig(context.Context, *AdminUpdateHumanRoomRobotConfigRequest) (*AdminUpdateHumanRoomRobotConfigResponse, error)
AdminCreateRoomPin(context.Context, *AdminCreateRoomPinRequest) (*AdminCreateRoomPinResponse, error)
AdminCancelRoomPin(context.Context, *AdminCancelRoomPinRequest) (*AdminCancelRoomPinResponse, error)
AdminCreateRobotRoom(context.Context, *AdminCreateRobotRoomRequest) (*AdminCreateRobotRoomResponse, error)
@ -496,106 +509,109 @@ type RoomCommandServiceServer interface {
type UnimplementedRoomCommandServiceServer struct{}
func (UnimplementedRoomCommandServiceServer) CreateRoom(context.Context, *CreateRoomRequest) (*CreateRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateRoomProfile not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateRoomProfile not implemented")
}
func (UnimplementedRoomCommandServiceServer) SaveRoomBackground(context.Context, *SaveRoomBackgroundRequest) (*SaveRoomBackgroundResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SaveRoomBackground not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SaveRoomBackground not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetRoomBackground(context.Context, *SetRoomBackgroundRequest) (*SetRoomBackgroundResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetRoomBackground not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetRoomBackground not implemented")
}
func (UnimplementedRoomCommandServiceServer) JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method JoinRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method JoinRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) RoomHeartbeat(context.Context, *RoomHeartbeatRequest) (*RoomHeartbeatResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RoomHeartbeat not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RoomHeartbeat not implemented")
}
func (UnimplementedRoomCommandServiceServer) LeaveRoom(context.Context, *LeaveRoomRequest) (*LeaveRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method LeaveRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method LeaveRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) CloseRoom(context.Context, *CloseRoomRequest) (*CloseRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CloseRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CloseRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminUpdateRoom(context.Context, *AdminUpdateRoomRequest) (*AdminUpdateRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminUpdateRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminDeleteRoom(context.Context, *AdminDeleteRoomRequest) (*AdminDeleteRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminDeleteRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminDeleteRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminUpdateRoomRocketConfig(context.Context, *AdminUpdateRoomRocketConfigRequest) (*AdminUpdateRoomRocketConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminUpdateRoomRocketConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateRoomRocketConfig not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminUpdateRoomSeatConfig(context.Context, *AdminUpdateRoomSeatConfigRequest) (*AdminUpdateRoomSeatConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminUpdateRoomSeatConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateRoomSeatConfig not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminUpdateHumanRoomRobotConfig(context.Context, *AdminUpdateHumanRoomRobotConfigRequest) (*AdminUpdateHumanRoomRobotConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateHumanRoomRobotConfig not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminCreateRoomPin(context.Context, *AdminCreateRoomPinRequest) (*AdminCreateRoomPinResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminCreateRoomPin not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminCreateRoomPin not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminCancelRoomPin(context.Context, *AdminCancelRoomPinRequest) (*AdminCancelRoomPinResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminCancelRoomPin not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminCancelRoomPin not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminCreateRobotRoom(context.Context, *AdminCreateRobotRoomRequest) (*AdminCreateRobotRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminCreateRobotRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminCreateRobotRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminSetRobotRoomStatus(context.Context, *AdminSetRobotRoomStatusRequest) (*AdminSetRobotRoomStatusResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminSetRobotRoomStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminSetRobotRoomStatus not implemented")
}
func (UnimplementedRoomCommandServiceServer) MicUp(context.Context, *MicUpRequest) (*MicUpResponse, error) {
return nil, status.Error(codes.Unimplemented, "method MicUp not implemented")
return nil, status.Errorf(codes.Unimplemented, "method MicUp not implemented")
}
func (UnimplementedRoomCommandServiceServer) MicDown(context.Context, *MicDownRequest) (*MicDownResponse, error) {
return nil, status.Error(codes.Unimplemented, "method MicDown not implemented")
return nil, status.Errorf(codes.Unimplemented, "method MicDown not implemented")
}
func (UnimplementedRoomCommandServiceServer) ChangeMicSeat(context.Context, *ChangeMicSeatRequest) (*ChangeMicSeatResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ChangeMicSeat not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ChangeMicSeat not implemented")
}
func (UnimplementedRoomCommandServiceServer) ConfirmMicPublishing(context.Context, *ConfirmMicPublishingRequest) (*ConfirmMicPublishingResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ConfirmMicPublishing not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ConfirmMicPublishing not implemented")
}
func (UnimplementedRoomCommandServiceServer) MicHeartbeat(context.Context, *MicHeartbeatRequest) (*MicHeartbeatResponse, error) {
return nil, status.Error(codes.Unimplemented, "method MicHeartbeat not implemented")
return nil, status.Errorf(codes.Unimplemented, "method MicHeartbeat not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetMicMute(context.Context, *SetMicMuteRequest) (*SetMicMuteResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetMicMute not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetMicMute not implemented")
}
func (UnimplementedRoomCommandServiceServer) ApplyRTCEvent(context.Context, *ApplyRTCEventRequest) (*ApplyRTCEventResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ApplyRTCEvent not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ApplyRTCEvent not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetMicSeatLock(context.Context, *SetMicSeatLockRequest) (*SetMicSeatLockResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetMicSeatLock not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetMicSeatLock not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetChatEnabled(context.Context, *SetChatEnabledRequest) (*SetChatEnabledResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetChatEnabled not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetChatEnabled not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetRoomPassword(context.Context, *SetRoomPasswordRequest) (*SetRoomPasswordResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetRoomPassword not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetRoomPassword not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetRoomAdmin(context.Context, *SetRoomAdminRequest) (*SetRoomAdminResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetRoomAdmin not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetRoomAdmin not implemented")
}
func (UnimplementedRoomCommandServiceServer) MuteUser(context.Context, *MuteUserRequest) (*MuteUserResponse, error) {
return nil, status.Error(codes.Unimplemented, "method MuteUser not implemented")
return nil, status.Errorf(codes.Unimplemented, "method MuteUser not implemented")
}
func (UnimplementedRoomCommandServiceServer) KickUser(context.Context, *KickUserRequest) (*KickUserResponse, error) {
return nil, status.Error(codes.Unimplemented, "method KickUser not implemented")
return nil, status.Errorf(codes.Unimplemented, "method KickUser not implemented")
}
func (UnimplementedRoomCommandServiceServer) UnbanUser(context.Context, *UnbanUserRequest) (*UnbanUserResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UnbanUser not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UnbanUser not implemented")
}
func (UnimplementedRoomCommandServiceServer) SystemEvictUser(context.Context, *SystemEvictUserRequest) (*SystemEvictUserResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SystemEvictUser not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SystemEvictUser not implemented")
}
func (UnimplementedRoomCommandServiceServer) SendGift(context.Context, *SendGiftRequest) (*SendGiftResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SendGift not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SendGift not implemented")
}
func (UnimplementedRoomCommandServiceServer) FollowRoom(context.Context, *FollowRoomRequest) (*FollowRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method FollowRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method FollowRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) UnfollowRoom(context.Context, *UnfollowRoomRequest) (*UnfollowRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UnfollowRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UnfollowRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) mustEmbedUnimplementedRoomCommandServiceServer() {}
func (UnimplementedRoomCommandServiceServer) testEmbeddedByValue() {}
@ -608,7 +624,7 @@ type UnsafeRoomCommandServiceServer interface {
}
func RegisterRoomCommandServiceServer(s grpc.ServiceRegistrar, srv RoomCommandServiceServer) {
// If the following call panics, it indicates UnimplementedRoomCommandServiceServer was
// If the following call pancis, it indicates UnimplementedRoomCommandServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -834,6 +850,24 @@ func _RoomCommandService_AdminUpdateRoomSeatConfig_Handler(srv interface{}, ctx
return interceptor(ctx, in, info, handler)
}
func _RoomCommandService_AdminUpdateHumanRoomRobotConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AdminUpdateHumanRoomRobotConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoomCommandServiceServer).AdminUpdateHumanRoomRobotConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: RoomCommandService_AdminUpdateHumanRoomRobotConfig_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoomCommandServiceServer).AdminUpdateHumanRoomRobotConfig(ctx, req.(*AdminUpdateHumanRoomRobotConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RoomCommandService_AdminCreateRoomPin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AdminCreateRoomPinRequest)
if err := dec(in); err != nil {
@ -1285,6 +1319,10 @@ var RoomCommandService_ServiceDesc = grpc.ServiceDesc{
MethodName: "AdminUpdateRoomSeatConfig",
Handler: _RoomCommandService_AdminUpdateRoomSeatConfig_Handler,
},
{
MethodName: "AdminUpdateHumanRoomRobotConfig",
Handler: _RoomCommandService_AdminUpdateHumanRoomRobotConfig_Handler,
},
{
MethodName: "AdminCreateRoomPin",
Handler: _RoomCommandService_AdminCreateRoomPin_Handler,
@ -1440,10 +1478,10 @@ type RoomGuardServiceServer interface {
type UnimplementedRoomGuardServiceServer struct{}
func (UnimplementedRoomGuardServiceServer) CheckSpeakPermission(context.Context, *CheckSpeakPermissionRequest) (*CheckSpeakPermissionResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CheckSpeakPermission not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CheckSpeakPermission not implemented")
}
func (UnimplementedRoomGuardServiceServer) VerifyRoomPresence(context.Context, *VerifyRoomPresenceRequest) (*VerifyRoomPresenceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method VerifyRoomPresence not implemented")
return nil, status.Errorf(codes.Unimplemented, "method VerifyRoomPresence not implemented")
}
func (UnimplementedRoomGuardServiceServer) mustEmbedUnimplementedRoomGuardServiceServer() {}
func (UnimplementedRoomGuardServiceServer) testEmbeddedByValue() {}
@ -1456,7 +1494,7 @@ type UnsafeRoomGuardServiceServer interface {
}
func RegisterRoomGuardServiceServer(s grpc.ServiceRegistrar, srv RoomGuardServiceServer) {
// If the following call panics, it indicates UnimplementedRoomGuardServiceServer was
// If the following call pancis, it indicates UnimplementedRoomGuardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1527,6 +1565,7 @@ const (
RoomQueryService_AdminGetRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/AdminGetRoom"
RoomQueryService_AdminGetRoomRocketConfig_FullMethodName = "/hyapp.room.v1.RoomQueryService/AdminGetRoomRocketConfig"
RoomQueryService_AdminGetRoomSeatConfig_FullMethodName = "/hyapp.room.v1.RoomQueryService/AdminGetRoomSeatConfig"
RoomQueryService_AdminGetHumanRoomRobotConfig_FullMethodName = "/hyapp.room.v1.RoomQueryService/AdminGetHumanRoomRobotConfig"
RoomQueryService_AdminListRoomPins_FullMethodName = "/hyapp.room.v1.RoomQueryService/AdminListRoomPins"
RoomQueryService_AdminListRobotRooms_FullMethodName = "/hyapp.room.v1.RoomQueryService/AdminListRobotRooms"
RoomQueryService_AdminFilterAvailableRoomRobots_FullMethodName = "/hyapp.room.v1.RoomQueryService/AdminFilterAvailableRoomRobots"
@ -1552,6 +1591,7 @@ type RoomQueryServiceClient interface {
AdminGetRoom(ctx context.Context, in *AdminGetRoomRequest, opts ...grpc.CallOption) (*AdminGetRoomResponse, error)
AdminGetRoomRocketConfig(ctx context.Context, in *AdminGetRoomRocketConfigRequest, opts ...grpc.CallOption) (*AdminGetRoomRocketConfigResponse, error)
AdminGetRoomSeatConfig(ctx context.Context, in *AdminGetRoomSeatConfigRequest, opts ...grpc.CallOption) (*AdminGetRoomSeatConfigResponse, error)
AdminGetHumanRoomRobotConfig(ctx context.Context, in *AdminGetHumanRoomRobotConfigRequest, opts ...grpc.CallOption) (*AdminGetHumanRoomRobotConfigResponse, error)
AdminListRoomPins(ctx context.Context, in *AdminListRoomPinsRequest, opts ...grpc.CallOption) (*AdminListRoomPinsResponse, error)
AdminListRobotRooms(ctx context.Context, in *AdminListRobotRoomsRequest, opts ...grpc.CallOption) (*AdminListRobotRoomsResponse, error)
AdminFilterAvailableRoomRobots(ctx context.Context, in *AdminFilterAvailableRoomRobotsRequest, opts ...grpc.CallOption) (*AdminFilterAvailableRoomRobotsResponse, error)
@ -1615,6 +1655,16 @@ func (c *roomQueryServiceClient) AdminGetRoomSeatConfig(ctx context.Context, in
return out, nil
}
func (c *roomQueryServiceClient) AdminGetHumanRoomRobotConfig(ctx context.Context, in *AdminGetHumanRoomRobotConfigRequest, opts ...grpc.CallOption) (*AdminGetHumanRoomRobotConfigResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AdminGetHumanRoomRobotConfigResponse)
err := c.cc.Invoke(ctx, RoomQueryService_AdminGetHumanRoomRobotConfig_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roomQueryServiceClient) AdminListRoomPins(ctx context.Context, in *AdminListRoomPinsRequest, opts ...grpc.CallOption) (*AdminListRoomPinsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AdminListRoomPinsResponse)
@ -1755,6 +1805,7 @@ type RoomQueryServiceServer interface {
AdminGetRoom(context.Context, *AdminGetRoomRequest) (*AdminGetRoomResponse, error)
AdminGetRoomRocketConfig(context.Context, *AdminGetRoomRocketConfigRequest) (*AdminGetRoomRocketConfigResponse, error)
AdminGetRoomSeatConfig(context.Context, *AdminGetRoomSeatConfigRequest) (*AdminGetRoomSeatConfigResponse, error)
AdminGetHumanRoomRobotConfig(context.Context, *AdminGetHumanRoomRobotConfigRequest) (*AdminGetHumanRoomRobotConfigResponse, error)
AdminListRoomPins(context.Context, *AdminListRoomPinsRequest) (*AdminListRoomPinsResponse, error)
AdminListRobotRooms(context.Context, *AdminListRobotRoomsRequest) (*AdminListRobotRoomsResponse, error)
AdminFilterAvailableRoomRobots(context.Context, *AdminFilterAvailableRoomRobotsRequest) (*AdminFilterAvailableRoomRobotsResponse, error)
@ -1779,55 +1830,58 @@ type RoomQueryServiceServer interface {
type UnimplementedRoomQueryServiceServer struct{}
func (UnimplementedRoomQueryServiceServer) AdminListRooms(context.Context, *AdminListRoomsRequest) (*AdminListRoomsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminListRooms not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminListRooms not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminGetRoom(context.Context, *AdminGetRoomRequest) (*AdminGetRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminGetRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminGetRoom not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminGetRoomRocketConfig(context.Context, *AdminGetRoomRocketConfigRequest) (*AdminGetRoomRocketConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminGetRoomRocketConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminGetRoomRocketConfig not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminGetRoomSeatConfig(context.Context, *AdminGetRoomSeatConfigRequest) (*AdminGetRoomSeatConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminGetRoomSeatConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminGetRoomSeatConfig not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminGetHumanRoomRobotConfig(context.Context, *AdminGetHumanRoomRobotConfigRequest) (*AdminGetHumanRoomRobotConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminGetHumanRoomRobotConfig not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminListRoomPins(context.Context, *AdminListRoomPinsRequest) (*AdminListRoomPinsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminListRoomPins not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminListRoomPins not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminListRobotRooms(context.Context, *AdminListRobotRoomsRequest) (*AdminListRobotRoomsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminListRobotRooms not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminListRobotRooms not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminFilterAvailableRoomRobots(context.Context, *AdminFilterAvailableRoomRobotsRequest) (*AdminFilterAvailableRoomRobotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminFilterAvailableRoomRobots not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminFilterAvailableRoomRobots not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRooms(context.Context, *ListRoomsRequest) (*ListRoomsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRooms not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRooms not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomFeeds(context.Context, *ListRoomFeedsRequest) (*ListRoomsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRoomFeeds not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRoomFeeds not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomGiftLeaderboard(context.Context, *ListRoomGiftLeaderboardRequest) (*ListRoomGiftLeaderboardResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRoomGiftLeaderboard not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRoomGiftLeaderboard not implemented")
}
func (UnimplementedRoomQueryServiceServer) GetMyRoom(context.Context, *GetMyRoomRequest) (*GetMyRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetMyRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetMyRoom not implemented")
}
func (UnimplementedRoomQueryServiceServer) GetCurrentRoom(context.Context, *GetCurrentRoomRequest) (*GetCurrentRoomResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetCurrentRoom not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetCurrentRoom not implemented")
}
func (UnimplementedRoomQueryServiceServer) GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRoomSnapshot not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetRoomSnapshot not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomBackgrounds(context.Context, *ListRoomBackgroundsRequest) (*ListRoomBackgroundsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRoomBackgrounds not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRoomBackgrounds not implemented")
}
func (UnimplementedRoomQueryServiceServer) GetRoomRocket(context.Context, *GetRoomRocketRequest) (*GetRoomRocketResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRoomRocket not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRocket not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRoomOnlineUsers not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRoomOnlineUsers not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomBannedUsers(context.Context, *ListRoomBannedUsersRequest) (*ListRoomBannedUsersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRoomBannedUsers not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRoomBannedUsers not implemented")
}
func (UnimplementedRoomQueryServiceServer) mustEmbedUnimplementedRoomQueryServiceServer() {}
func (UnimplementedRoomQueryServiceServer) testEmbeddedByValue() {}
@ -1840,7 +1894,7 @@ type UnsafeRoomQueryServiceServer interface {
}
func RegisterRoomQueryServiceServer(s grpc.ServiceRegistrar, srv RoomQueryServiceServer) {
// If the following call panics, it indicates UnimplementedRoomQueryServiceServer was
// If the following call pancis, it indicates UnimplementedRoomQueryServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1922,6 +1976,24 @@ func _RoomQueryService_AdminGetRoomSeatConfig_Handler(srv interface{}, ctx conte
return interceptor(ctx, in, info, handler)
}
func _RoomQueryService_AdminGetHumanRoomRobotConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AdminGetHumanRoomRobotConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoomQueryServiceServer).AdminGetHumanRoomRobotConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: RoomQueryService_AdminGetHumanRoomRobotConfig_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoomQueryServiceServer).AdminGetHumanRoomRobotConfig(ctx, req.(*AdminGetHumanRoomRobotConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RoomQueryService_AdminListRoomPins_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AdminListRoomPinsRequest)
if err := dec(in); err != nil {
@ -2179,6 +2251,10 @@ var RoomQueryService_ServiceDesc = grpc.ServiceDesc{
MethodName: "AdminGetRoomSeatConfig",
Handler: _RoomQueryService_AdminGetRoomSeatConfig_Handler,
},
{
MethodName: "AdminGetHumanRoomRobotConfig",
Handler: _RoomQueryService_AdminGetHumanRoomRobotConfig_Handler,
},
{
MethodName: "AdminListRoomPins",
Handler: _RoomQueryService_AdminListRoomPins_Handler,

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc-gen-go v1.35.1
// protoc v7.35.0
// source: proto/user/v1/auth.proto
@ -11,7 +11,6 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@ -23,12 +22,13 @@ const (
// LoginPasswordRequest 使用当前 display_user_id 和已设置密码登录。
type LoginPasswordRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
}
func (x *LoginPasswordRequest) Reset() {
@ -84,29 +84,30 @@ func (x *LoginPasswordRequest) GetPassword() string {
// LoginThirdPartyRequest 使用三方凭证登录或注册。
type LoginThirdPartyRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
Credential string `protobuf:"bytes,3,opt,name=credential,proto3" json:"credential,omitempty"`
Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"`
Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"`
DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Device string `protobuf:"bytes,9,opt,name=device,proto3" json:"device,omitempty"`
Avatar string `protobuf:"bytes,10,opt,name=avatar,proto3" json:"avatar,omitempty"`
Birth string `protobuf:"bytes,11,opt,name=birth,proto3" json:"birth,omitempty"`
AppVersion string `protobuf:"bytes,12,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
Source string `protobuf:"bytes,13,opt,name=source,proto3" json:"source,omitempty"`
Platform string `protobuf:"bytes,14,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,15,opt,name=language,proto3" json:"language,omitempty"`
OsVersion string `protobuf:"bytes,16,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"`
BuildNumber string `protobuf:"bytes,17,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"`
Timezone string `protobuf:"bytes,18,opt,name=timezone,proto3" json:"timezone,omitempty"`
InstallChannel string `protobuf:"bytes,19,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"`
Campaign string `protobuf:"bytes,20,opt,name=campaign,proto3" json:"campaign,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
Credential string `protobuf:"bytes,3,opt,name=credential,proto3" json:"credential,omitempty"`
Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"`
Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"`
DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Device string `protobuf:"bytes,9,opt,name=device,proto3" json:"device,omitempty"`
Avatar string `protobuf:"bytes,10,opt,name=avatar,proto3" json:"avatar,omitempty"`
Birth string `protobuf:"bytes,11,opt,name=birth,proto3" json:"birth,omitempty"`
AppVersion string `protobuf:"bytes,12,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
Source string `protobuf:"bytes,13,opt,name=source,proto3" json:"source,omitempty"`
Platform string `protobuf:"bytes,14,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,15,opt,name=language,proto3" json:"language,omitempty"`
OsVersion string `protobuf:"bytes,16,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"`
BuildNumber string `protobuf:"bytes,17,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"`
Timezone string `protobuf:"bytes,18,opt,name=timezone,proto3" json:"timezone,omitempty"`
InstallChannel string `protobuf:"bytes,19,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"`
Campaign string `protobuf:"bytes,20,opt,name=campaign,proto3" json:"campaign,omitempty"`
}
func (x *LoginThirdPartyRequest) Reset() {
@ -281,13 +282,14 @@ func (x *LoginThirdPartyRequest) GetCampaign() string {
// AuthResponse 返回标准认证令牌。
type AuthResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
IsNewUser bool `protobuf:"varint,2,opt,name=is_new_user,json=isNewUser,proto3" json:"is_new_user,omitempty"`
ProfileCompleted bool `protobuf:"varint,3,opt,name=profile_completed,json=profileCompleted,proto3" json:"profile_completed,omitempty"`
OnboardingStatus string `protobuf:"bytes,4,opt,name=onboarding_status,json=onboardingStatus,proto3" json:"onboarding_status,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
IsNewUser bool `protobuf:"varint,2,opt,name=is_new_user,json=isNewUser,proto3" json:"is_new_user,omitempty"`
ProfileCompleted bool `protobuf:"varint,3,opt,name=profile_completed,json=profileCompleted,proto3" json:"profile_completed,omitempty"`
OnboardingStatus string `protobuf:"bytes,4,opt,name=onboarding_status,json=onboardingStatus,proto3" json:"onboarding_status,omitempty"`
}
func (x *AuthResponse) Reset() {
@ -350,12 +352,13 @@ func (x *AuthResponse) GetOnboardingStatus() string {
// SetPasswordRequest 为已经三方登录的用户首次设置密码。
type SetPasswordRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
}
func (x *SetPasswordRequest) Reset() {
@ -411,10 +414,11 @@ func (x *SetPasswordRequest) GetPassword() string {
// SetPasswordResponse 返回密码是否已经成功设置。
type SetPasswordResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
PasswordSet bool `protobuf:"varint,1,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PasswordSet bool `protobuf:"varint,1,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"`
}
func (x *SetPasswordResponse) Reset() {
@ -456,28 +460,29 @@ func (x *SetPasswordResponse) GetPasswordSet() bool {
// QuickCreateAccountRequest 为后台快速创建完整资料账号source=game_robot 时账号不可登录。
type QuickCreateAccountRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
Avatar string `protobuf:"bytes,4,opt,name=avatar,proto3" json:"avatar,omitempty"`
Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"`
Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"`
DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Device string `protobuf:"bytes,9,opt,name=device,proto3" json:"device,omitempty"`
OsVersion string `protobuf:"bytes,10,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"`
Birth string `protobuf:"bytes,11,opt,name=birth,proto3" json:"birth,omitempty"`
AppVersion string `protobuf:"bytes,12,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
BuildNumber string `protobuf:"bytes,13,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"`
Source string `protobuf:"bytes,14,opt,name=source,proto3" json:"source,omitempty"`
InstallChannel string `protobuf:"bytes,15,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"`
Campaign string `protobuf:"bytes,16,opt,name=campaign,proto3" json:"campaign,omitempty"`
Platform string `protobuf:"bytes,17,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,18,opt,name=language,proto3" json:"language,omitempty"`
Timezone string `protobuf:"bytes,19,opt,name=timezone,proto3" json:"timezone,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
Avatar string `protobuf:"bytes,4,opt,name=avatar,proto3" json:"avatar,omitempty"`
Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"`
Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"`
DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Device string `protobuf:"bytes,9,opt,name=device,proto3" json:"device,omitempty"`
OsVersion string `protobuf:"bytes,10,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"`
Birth string `protobuf:"bytes,11,opt,name=birth,proto3" json:"birth,omitempty"`
AppVersion string `protobuf:"bytes,12,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
BuildNumber string `protobuf:"bytes,13,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"`
Source string `protobuf:"bytes,14,opt,name=source,proto3" json:"source,omitempty"`
InstallChannel string `protobuf:"bytes,15,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"`
Campaign string `protobuf:"bytes,16,opt,name=campaign,proto3" json:"campaign,omitempty"`
Platform string `protobuf:"bytes,17,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,18,opt,name=language,proto3" json:"language,omitempty"`
Timezone string `protobuf:"bytes,19,opt,name=timezone,proto3" json:"timezone,omitempty"`
}
func (x *QuickCreateAccountRequest) Reset() {
@ -645,11 +650,12 @@ func (x *QuickCreateAccountRequest) GetTimezone() string {
// QuickCreateAccountResponse 返回新账号身份普通账号含登录态source=game_robot 时 token 只含 user_id/display_user_id。
type QuickCreateAccountResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
PasswordSet bool `protobuf:"varint,2,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
PasswordSet bool `protobuf:"varint,2,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"`
}
func (x *QuickCreateAccountResponse) Reset() {
@ -698,11 +704,12 @@ func (x *QuickCreateAccountResponse) GetPasswordSet() bool {
// RefreshTokenRequest 使用 refresh token 换取新 token。
type RefreshTokenRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
}
func (x *RefreshTokenRequest) Reset() {
@ -751,10 +758,11 @@ func (x *RefreshTokenRequest) GetRefreshToken() string {
// RefreshTokenResponse 返回轮换后的令牌。
type RefreshTokenResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
}
func (x *RefreshTokenResponse) Reset() {
@ -796,12 +804,13 @@ func (x *RefreshTokenResponse) GetToken() *AuthToken {
// LogoutRequest 失效指定会话或 refresh token。
type LogoutRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
}
func (x *LogoutRequest) Reset() {
@ -857,10 +866,11 @@ func (x *LogoutRequest) GetRefreshToken() string {
// LogoutResponse 返回会话是否被失效。
type LogoutResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"`
}
func (x *LogoutResponse) Reset() {
@ -902,15 +912,16 @@ func (x *LogoutResponse) GetRevoked() bool {
// RecordLoginBlockedRequest 记录 gateway 入口快拦拒绝的登录审计。
type RecordLoginBlockedRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Channel string `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty"`
Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,4,opt,name=language,proto3" json:"language,omitempty"`
Timezone string `protobuf:"bytes,5,opt,name=timezone,proto3" json:"timezone,omitempty"`
BlockReason string `protobuf:"bytes,6,opt,name=block_reason,json=blockReason,proto3" json:"block_reason,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Channel string `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty"`
Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,4,opt,name=language,proto3" json:"language,omitempty"`
Timezone string `protobuf:"bytes,5,opt,name=timezone,proto3" json:"timezone,omitempty"`
BlockReason string `protobuf:"bytes,6,opt,name=block_reason,json=blockReason,proto3" json:"block_reason,omitempty"`
}
func (x *RecordLoginBlockedRequest) Reset() {
@ -987,10 +998,11 @@ func (x *RecordLoginBlockedRequest) GetBlockReason() string {
// RecordLoginBlockedResponse 返回审计写入是否成功。
type RecordLoginBlockedResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Recorded bool `protobuf:"varint,1,opt,name=recorded,proto3" json:"recorded,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Recorded bool `protobuf:"varint,1,opt,name=recorded,proto3" json:"recorded,omitempty"`
}
func (x *RecordLoginBlockedResponse) Reset() {
@ -1032,12 +1044,13 @@ func (x *RecordLoginBlockedResponse) GetRecorded() bool {
// AppHeartbeatRequest 刷新当前登录会话的 App 在线心跳。
type AppHeartbeatRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
}
func (x *AppHeartbeatRequest) Reset() {
@ -1093,13 +1106,14 @@ func (x *AppHeartbeatRequest) GetSessionId() string {
// AppHeartbeatResponse 返回本次会话心跳写入时间,并在当前会话仍有效时顺手重签 access token。
type AppHeartbeatResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"`
HeartbeatAtMs int64 `protobuf:"varint,2,opt,name=heartbeat_at_ms,json=heartbeatAtMs,proto3" json:"heartbeat_at_ms,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
Token *AuthToken `protobuf:"bytes,4,opt,name=token,proto3" json:"token,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"`
HeartbeatAtMs int64 `protobuf:"varint,2,opt,name=heartbeat_at_ms,json=heartbeatAtMs,proto3" json:"heartbeat_at_ms,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
Token *AuthToken `protobuf:"bytes,4,opt,name=token,proto3" json:"token,omitempty"`
}
func (x *AppHeartbeatResponse) Reset() {
@ -1162,127 +1176,244 @@ func (x *AppHeartbeatResponse) GetToken() *AuthToken {
var File_proto_user_v1_auth_proto protoreflect.FileDescriptor
const file_proto_user_v1_auth_proto_rawDesc = "" +
"\n" +
"\x18proto/user/v1/auth.proto\x12\rhyapp.user.v1\x1a\x18proto/user/v1/user.proto\"\x8a\x01\n" +
"\x14LoginPasswordRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12&\n" +
"\x0fdisplay_user_id\x18\x02 \x01(\tR\rdisplayUserId\x12\x1a\n" +
"\bpassword\x18\x03 \x01(\tR\bpassword\"\xea\x04\n" +
"\x16LoginThirdPartyRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1a\n" +
"\bprovider\x18\x02 \x01(\tR\bprovider\x12\x1e\n" +
"\n" +
"credential\x18\x03 \x01(\tR\n" +
"credential\x12\x1a\n" +
"\busername\x18\x04 \x01(\tR\busername\x12\x16\n" +
"\x06gender\x18\x05 \x01(\tR\x06gender\x12\x18\n" +
"\acountry\x18\x06 \x01(\tR\acountry\x12\x1f\n" +
"\vinvite_code\x18\a \x01(\tR\n" +
"inviteCode\x12\x1b\n" +
"\tdevice_id\x18\b \x01(\tR\bdeviceId\x12\x16\n" +
"\x06device\x18\t \x01(\tR\x06device\x12\x16\n" +
"\x06avatar\x18\n" +
" \x01(\tR\x06avatar\x12\x14\n" +
"\x05birth\x18\v \x01(\tR\x05birth\x12\x1f\n" +
"\vapp_version\x18\f \x01(\tR\n" +
"appVersion\x12\x16\n" +
"\x06source\x18\r \x01(\tR\x06source\x12\x1a\n" +
"\bplatform\x18\x0e \x01(\tR\bplatform\x12\x1a\n" +
"\blanguage\x18\x0f \x01(\tR\blanguage\x12\x1d\n" +
"\n" +
"os_version\x18\x10 \x01(\tR\tosVersion\x12!\n" +
"\fbuild_number\x18\x11 \x01(\tR\vbuildNumber\x12\x1a\n" +
"\btimezone\x18\x12 \x01(\tR\btimezone\x12'\n" +
"\x0finstall_channel\x18\x13 \x01(\tR\x0einstallChannel\x12\x1a\n" +
"\bcampaign\x18\x14 \x01(\tR\bcampaign\"\xb8\x01\n" +
"\fAuthResponse\x12.\n" +
"\x05token\x18\x01 \x01(\v2\x18.hyapp.user.v1.AuthTokenR\x05token\x12\x1e\n" +
"\vis_new_user\x18\x02 \x01(\bR\tisNewUser\x12+\n" +
"\x11profile_completed\x18\x03 \x01(\bR\x10profileCompleted\x12+\n" +
"\x11onboarding_status\x18\x04 \x01(\tR\x10onboardingStatus\"y\n" +
"\x12SetPasswordRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" +
"\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1a\n" +
"\bpassword\x18\x03 \x01(\tR\bpassword\"8\n" +
"\x13SetPasswordResponse\x12!\n" +
"\fpassword_set\x18\x01 \x01(\bR\vpasswordSet\"\xcd\x04\n" +
"\x19QuickCreateAccountRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1a\n" +
"\bpassword\x18\x02 \x01(\tR\bpassword\x12\x1a\n" +
"\busername\x18\x03 \x01(\tR\busername\x12\x16\n" +
"\x06avatar\x18\x04 \x01(\tR\x06avatar\x12\x16\n" +
"\x06gender\x18\x05 \x01(\tR\x06gender\x12\x18\n" +
"\acountry\x18\x06 \x01(\tR\acountry\x12\x1f\n" +
"\vinvite_code\x18\a \x01(\tR\n" +
"inviteCode\x12\x1b\n" +
"\tdevice_id\x18\b \x01(\tR\bdeviceId\x12\x16\n" +
"\x06device\x18\t \x01(\tR\x06device\x12\x1d\n" +
"\n" +
"os_version\x18\n" +
" \x01(\tR\tosVersion\x12\x14\n" +
"\x05birth\x18\v \x01(\tR\x05birth\x12\x1f\n" +
"\vapp_version\x18\f \x01(\tR\n" +
"appVersion\x12!\n" +
"\fbuild_number\x18\r \x01(\tR\vbuildNumber\x12\x16\n" +
"\x06source\x18\x0e \x01(\tR\x06source\x12'\n" +
"\x0finstall_channel\x18\x0f \x01(\tR\x0einstallChannel\x12\x1a\n" +
"\bcampaign\x18\x10 \x01(\tR\bcampaign\x12\x1a\n" +
"\bplatform\x18\x11 \x01(\tR\bplatform\x12\x1a\n" +
"\blanguage\x18\x12 \x01(\tR\blanguage\x12\x1a\n" +
"\btimezone\x18\x13 \x01(\tR\btimezone\"o\n" +
"\x1aQuickCreateAccountResponse\x12.\n" +
"\x05token\x18\x01 \x01(\v2\x18.hyapp.user.v1.AuthTokenR\x05token\x12!\n" +
"\fpassword_set\x18\x02 \x01(\bR\vpasswordSet\"j\n" +
"\x13RefreshTokenRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12#\n" +
"\rrefresh_token\x18\x02 \x01(\tR\frefreshToken\"F\n" +
"\x14RefreshTokenResponse\x12.\n" +
"\x05token\x18\x01 \x01(\v2\x18.hyapp.user.v1.AuthTokenR\x05token\"\x83\x01\n" +
"\rLogoutRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1d\n" +
"\n" +
"session_id\x18\x02 \x01(\tR\tsessionId\x12#\n" +
"\rrefresh_token\x18\x03 \x01(\tR\frefreshToken\"*\n" +
"\x0eLogoutResponse\x12\x18\n" +
"\arevoked\x18\x01 \x01(\bR\arevoked\"\xdc\x01\n" +
"\x19RecordLoginBlockedRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x18\n" +
"\achannel\x18\x02 \x01(\tR\achannel\x12\x1a\n" +
"\bplatform\x18\x03 \x01(\tR\bplatform\x12\x1a\n" +
"\blanguage\x18\x04 \x01(\tR\blanguage\x12\x1a\n" +
"\btimezone\x18\x05 \x01(\tR\btimezone\x12!\n" +
"\fblock_reason\x18\x06 \x01(\tR\vblockReason\"8\n" +
"\x1aRecordLoginBlockedResponse\x12\x1a\n" +
"\brecorded\x18\x01 \x01(\bR\brecorded\"}\n" +
"\x13AppHeartbeatRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" +
"\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1d\n" +
"\n" +
"session_id\x18\x03 \x01(\tR\tsessionId\"\xb0\x01\n" +
"\x14AppHeartbeatResponse\x12\x1a\n" +
"\baccepted\x18\x01 \x01(\bR\baccepted\x12&\n" +
"\x0fheartbeat_at_ms\x18\x02 \x01(\x03R\rheartbeatAtMs\x12$\n" +
"\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\x12.\n" +
"\x05token\x18\x04 \x01(\v2\x18.hyapp.user.v1.AuthTokenR\x05token2\xdc\x05\n" +
"\vAuthService\x12Q\n" +
"\rLoginPassword\x12#.hyapp.user.v1.LoginPasswordRequest\x1a\x1b.hyapp.user.v1.AuthResponse\x12U\n" +
"\x0fLoginThirdParty\x12%.hyapp.user.v1.LoginThirdPartyRequest\x1a\x1b.hyapp.user.v1.AuthResponse\x12T\n" +
"\vSetPassword\x12!.hyapp.user.v1.SetPasswordRequest\x1a\".hyapp.user.v1.SetPasswordResponse\x12i\n" +
"\x12QuickCreateAccount\x12(.hyapp.user.v1.QuickCreateAccountRequest\x1a).hyapp.user.v1.QuickCreateAccountResponse\x12W\n" +
"\fRefreshToken\x12\".hyapp.user.v1.RefreshTokenRequest\x1a#.hyapp.user.v1.RefreshTokenResponse\x12E\n" +
"\x06Logout\x12\x1c.hyapp.user.v1.LogoutRequest\x1a\x1d.hyapp.user.v1.LogoutResponse\x12i\n" +
"\x12RecordLoginBlocked\x12(.hyapp.user.v1.RecordLoginBlockedRequest\x1a).hyapp.user.v1.RecordLoginBlockedResponse\x12W\n" +
"\fAppHeartbeat\x12\".hyapp.user.v1.AppHeartbeatRequest\x1a#.hyapp.user.v1.AppHeartbeatResponseB&Z$hyapp.local/api/proto/user/v1;userv1b\x06proto3"
var file_proto_user_v1_auth_proto_rawDesc = []byte{
0x0a, 0x18, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f,
0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x1a, 0x18, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0x8a, 0x01, 0x0a, 0x14, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04,
0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f,
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73,
0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x22, 0xea, 0x04, 0x0a, 0x16, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50,
0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d,
0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x72, 0x65,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f,
0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69,
0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63,
0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x09, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61,
0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61,
0x74, 0x61, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x0b, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70,
0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x0e,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a,
0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73,
0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69,
0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08,
0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74,
0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x13, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x18, 0x14, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x22, 0xb8, 0x01,
0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e,
0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75,
0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e,
0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20,
0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x4e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2b,
0x0a, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x66, 0x69,
0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x6f,
0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69,
0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x50,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e,
0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17,
0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x22, 0x38, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0b, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x53, 0x65, 0x74, 0x22, 0xcd, 0x04,
0x0a, 0x19, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d,
0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x70,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x67,
0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e,
0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1f, 0x0a,
0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b,
0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64,
0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76,
0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61,
0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69,
0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69,
0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x0a,
0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61,
0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61,
0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67,
0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x13, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x22, 0x6f, 0x0a,
0x1a, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54,
0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x70,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0b, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x53, 0x65, 0x74, 0x22, 0x6a,
0x0a, 0x13, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52,
0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68,
0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x46, 0x0a, 0x14, 0x52, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b,
0x65, 0x6e, 0x22, 0x83, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04,
0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72,
0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x2a, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x6f,
0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65,
0x76, 0x6f, 0x6b, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x76,
0x6f, 0x6b, 0x65, 0x64, 0x22, 0xdc, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c,
0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65,
0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08,
0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67,
0x75, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67,
0x75, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65,
0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e,
0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x61,
0x73, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67,
0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x22, 0x7d, 0x0a,
0x13, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04,
0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a,
0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xb0, 0x01, 0x0a,
0x14, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65,
0x64, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, 0x61,
0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x68, 0x65, 0x61, 0x72,
0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12,
0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41,
0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32,
0xdc, 0x05, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
0x51, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x55, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64,
0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64,
0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74,
0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74,
0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x69, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x52, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65,
0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52,
0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f,
0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f,
0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64,
0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63,
0x6b, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72,
0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65,
0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61,
0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26,
0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b,
0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proto_user_v1_auth_proto_rawDescOnce sync.Once
file_proto_user_v1_auth_proto_rawDescData []byte
file_proto_user_v1_auth_proto_rawDescData = file_proto_user_v1_auth_proto_rawDesc
)
func file_proto_user_v1_auth_proto_rawDescGZIP() []byte {
file_proto_user_v1_auth_proto_rawDescOnce.Do(func() {
file_proto_user_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_user_v1_auth_proto_rawDesc), len(file_proto_user_v1_auth_proto_rawDesc)))
file_proto_user_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_user_v1_auth_proto_rawDescData)
})
return file_proto_user_v1_auth_proto_rawDescData
}
@ -1353,7 +1484,7 @@ func file_proto_user_v1_auth_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_user_v1_auth_proto_rawDesc), len(file_proto_user_v1_auth_proto_rawDesc)),
RawDescriptor: file_proto_user_v1_auth_proto_rawDesc,
NumEnums: 0,
NumMessages: 15,
NumExtensions: 0,
@ -1364,6 +1495,7 @@ func file_proto_user_v1_auth_proto_init() {
MessageInfos: file_proto_user_v1_auth_proto_msgTypes,
}.Build()
File_proto_user_v1_auth_proto = out.File
file_proto_user_v1_auth_proto_rawDesc = nil
file_proto_user_v1_auth_proto_goTypes = nil
file_proto_user_v1_auth_proto_depIdxs = nil
}

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc-gen-go-grpc v1.5.1
// - protoc v7.35.0
// source: proto/user/v1/auth.proto
@ -158,28 +158,28 @@ type AuthServiceServer interface {
type UnimplementedAuthServiceServer struct{}
func (UnimplementedAuthServiceServer) LoginPassword(context.Context, *LoginPasswordRequest) (*AuthResponse, error) {
return nil, status.Error(codes.Unimplemented, "method LoginPassword not implemented")
return nil, status.Errorf(codes.Unimplemented, "method LoginPassword not implemented")
}
func (UnimplementedAuthServiceServer) LoginThirdParty(context.Context, *LoginThirdPartyRequest) (*AuthResponse, error) {
return nil, status.Error(codes.Unimplemented, "method LoginThirdParty not implemented")
return nil, status.Errorf(codes.Unimplemented, "method LoginThirdParty not implemented")
}
func (UnimplementedAuthServiceServer) SetPassword(context.Context, *SetPasswordRequest) (*SetPasswordResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetPassword not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetPassword not implemented")
}
func (UnimplementedAuthServiceServer) QuickCreateAccount(context.Context, *QuickCreateAccountRequest) (*QuickCreateAccountResponse, error) {
return nil, status.Error(codes.Unimplemented, "method QuickCreateAccount not implemented")
return nil, status.Errorf(codes.Unimplemented, "method QuickCreateAccount not implemented")
}
func (UnimplementedAuthServiceServer) RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RefreshToken not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RefreshToken not implemented")
}
func (UnimplementedAuthServiceServer) Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Logout not implemented")
return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented")
}
func (UnimplementedAuthServiceServer) RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RecordLoginBlocked not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RecordLoginBlocked not implemented")
}
func (UnimplementedAuthServiceServer) AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AppHeartbeat not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AppHeartbeat not implemented")
}
func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {}
func (UnimplementedAuthServiceServer) testEmbeddedByValue() {}
@ -192,7 +192,7 @@ type UnsafeAuthServiceServer interface {
}
func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) {
// If the following call panics, it indicates UnimplementedAuthServiceServer was
// If the following call pancis, it indicates UnimplementedAuthServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc-gen-go-grpc v1.5.1
// - protoc v7.35.0
// source: proto/user/v1/host.proto
@ -327,67 +327,67 @@ type UserHostServiceServer interface {
type UnimplementedUserHostServiceServer struct{}
func (UnimplementedUserHostServiceServer) SearchAgencies(context.Context, *SearchAgenciesRequest) (*SearchAgenciesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SearchAgencies not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SearchAgencies not implemented")
}
func (UnimplementedUserHostServiceServer) ApplyToAgency(context.Context, *ApplyToAgencyRequest) (*ApplyToAgencyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ApplyToAgency not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ApplyToAgency not implemented")
}
func (UnimplementedUserHostServiceServer) ReviewAgencyApplication(context.Context, *ReviewAgencyApplicationRequest) (*ReviewAgencyApplicationResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ReviewAgencyApplication not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ReviewAgencyApplication not implemented")
}
func (UnimplementedUserHostServiceServer) KickAgencyHost(context.Context, *KickAgencyHostRequest) (*KickAgencyHostResponse, error) {
return nil, status.Error(codes.Unimplemented, "method KickAgencyHost not implemented")
return nil, status.Errorf(codes.Unimplemented, "method KickAgencyHost not implemented")
}
func (UnimplementedUserHostServiceServer) InviteAgency(context.Context, *InviteAgencyRequest) (*InviteAgencyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method InviteAgency not implemented")
return nil, status.Errorf(codes.Unimplemented, "method InviteAgency not implemented")
}
func (UnimplementedUserHostServiceServer) InviteBD(context.Context, *InviteBDRequest) (*InviteBDResponse, error) {
return nil, status.Error(codes.Unimplemented, "method InviteBD not implemented")
return nil, status.Errorf(codes.Unimplemented, "method InviteBD not implemented")
}
func (UnimplementedUserHostServiceServer) InviteHost(context.Context, *InviteHostRequest) (*InviteHostResponse, error) {
return nil, status.Error(codes.Unimplemented, "method InviteHost not implemented")
return nil, status.Errorf(codes.Unimplemented, "method InviteHost not implemented")
}
func (UnimplementedUserHostServiceServer) ListBDLeaderBDs(context.Context, *ListBDLeaderBDsRequest) (*ListBDLeaderBDsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListBDLeaderBDs not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListBDLeaderBDs not implemented")
}
func (UnimplementedUserHostServiceServer) ListBDLeaderAgencies(context.Context, *ListBDLeaderAgenciesRequest) (*ListBDLeaderAgenciesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListBDLeaderAgencies not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListBDLeaderAgencies not implemented")
}
func (UnimplementedUserHostServiceServer) ListBDAgencies(context.Context, *ListBDAgenciesRequest) (*ListBDAgenciesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListBDAgencies not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListBDAgencies not implemented")
}
func (UnimplementedUserHostServiceServer) ListRoleInvitations(context.Context, *ListRoleInvitationsRequest) (*ListRoleInvitationsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRoleInvitations not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRoleInvitations not implemented")
}
func (UnimplementedUserHostServiceServer) ProcessRoleInvitation(context.Context, *ProcessRoleInvitationRequest) (*ProcessRoleInvitationResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ProcessRoleInvitation not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ProcessRoleInvitation not implemented")
}
func (UnimplementedUserHostServiceServer) GetHostProfile(context.Context, *GetHostProfileRequest) (*GetHostProfileResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetHostProfile not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetHostProfile not implemented")
}
func (UnimplementedUserHostServiceServer) GetBDProfile(context.Context, *GetBDProfileRequest) (*GetBDProfileResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetBDProfile not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetBDProfile not implemented")
}
func (UnimplementedUserHostServiceServer) GetCoinSellerProfile(context.Context, *GetCoinSellerProfileRequest) (*GetCoinSellerProfileResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetCoinSellerProfile not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetCoinSellerProfile not implemented")
}
func (UnimplementedUserHostServiceServer) ListActiveCoinSellersInMyRegion(context.Context, *ListActiveCoinSellersInMyRegionRequest) (*ListActiveCoinSellersInMyRegionResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListActiveCoinSellersInMyRegion not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListActiveCoinSellersInMyRegion not implemented")
}
func (UnimplementedUserHostServiceServer) GetUserRoleSummary(context.Context, *GetUserRoleSummaryRequest) (*GetUserRoleSummaryResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetUserRoleSummary not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetUserRoleSummary not implemented")
}
func (UnimplementedUserHostServiceServer) GetAgency(context.Context, *GetAgencyRequest) (*GetAgencyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetAgency not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetAgency not implemented")
}
func (UnimplementedUserHostServiceServer) CheckBusinessCapability(context.Context, *CheckBusinessCapabilityRequest) (*CheckBusinessCapabilityResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CheckBusinessCapability not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CheckBusinessCapability not implemented")
}
func (UnimplementedUserHostServiceServer) GetAgencyMembers(context.Context, *GetAgencyMembersRequest) (*GetAgencyMembersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetAgencyMembers not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyMembers not implemented")
}
func (UnimplementedUserHostServiceServer) GetAgencyApplications(context.Context, *GetAgencyApplicationsRequest) (*GetAgencyApplicationsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetAgencyApplications not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyApplications not implemented")
}
func (UnimplementedUserHostServiceServer) mustEmbedUnimplementedUserHostServiceServer() {}
func (UnimplementedUserHostServiceServer) testEmbeddedByValue() {}
@ -400,7 +400,7 @@ type UnsafeUserHostServiceServer interface {
}
func RegisterUserHostServiceServer(s grpc.ServiceRegistrar, srv UserHostServiceServer) {
// If the following call panics, it indicates UnimplementedUserHostServiceServer was
// If the following call pancis, it indicates UnimplementedUserHostServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1037,31 +1037,31 @@ type UserHostAdminServiceServer interface {
type UnimplementedUserHostAdminServiceServer struct{}
func (UnimplementedUserHostAdminServiceServer) CreateBDLeader(context.Context, *CreateBDLeaderRequest) (*CreateBDLeaderResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateBDLeader not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateBDLeader not implemented")
}
func (UnimplementedUserHostAdminServiceServer) CreateBD(context.Context, *CreateBDRequest) (*CreateBDResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateBD not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateBD not implemented")
}
func (UnimplementedUserHostAdminServiceServer) SetBDStatus(context.Context, *SetBDStatusRequest) (*SetBDStatusResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetBDStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetBDStatus not implemented")
}
func (UnimplementedUserHostAdminServiceServer) CreateCoinSeller(context.Context, *CreateCoinSellerRequest) (*CreateCoinSellerResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateCoinSeller not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateCoinSeller not implemented")
}
func (UnimplementedUserHostAdminServiceServer) SetCoinSellerStatus(context.Context, *SetCoinSellerStatusRequest) (*SetCoinSellerStatusResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetCoinSellerStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetCoinSellerStatus not implemented")
}
func (UnimplementedUserHostAdminServiceServer) CreateAgency(context.Context, *CreateAgencyRequest) (*CreateAgencyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateAgency not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateAgency not implemented")
}
func (UnimplementedUserHostAdminServiceServer) CloseAgency(context.Context, *CloseAgencyRequest) (*CloseAgencyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CloseAgency not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CloseAgency not implemented")
}
func (UnimplementedUserHostAdminServiceServer) DeleteAgency(context.Context, *DeleteAgencyRequest) (*DeleteAgencyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteAgency not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DeleteAgency not implemented")
}
func (UnimplementedUserHostAdminServiceServer) SetAgencyJoinEnabled(context.Context, *SetAgencyJoinEnabledRequest) (*SetAgencyJoinEnabledResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetAgencyJoinEnabled not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetAgencyJoinEnabled not implemented")
}
func (UnimplementedUserHostAdminServiceServer) mustEmbedUnimplementedUserHostAdminServiceServer() {}
func (UnimplementedUserHostAdminServiceServer) testEmbeddedByValue() {}
@ -1074,7 +1074,7 @@ type UnsafeUserHostAdminServiceServer interface {
}
func RegisterUserHostAdminServiceServer(s grpc.ServiceRegistrar, srv UserHostAdminServiceServer) {
// If the following call panics, it indicates UnimplementedUserHostAdminServiceServer was
// If the following call pancis, it indicates UnimplementedUserHostAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc-gen-go-grpc v1.5.1
// - protoc v7.35.0
// source: proto/user/v1/user.proto
@ -262,52 +262,52 @@ type UserServiceServer interface {
type UnimplementedUserServiceServer struct{}
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetUser not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
}
func (UnimplementedUserServiceServer) GetInviteAttribution(context.Context, *GetInviteAttributionRequest) (*GetInviteAttributionResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetInviteAttribution not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetInviteAttribution not implemented")
}
func (UnimplementedUserServiceServer) BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error) {
return nil, status.Error(codes.Unimplemented, "method BusinessUserLookup not implemented")
return nil, status.Errorf(codes.Unimplemented, "method BusinessUserLookup not implemented")
}
func (UnimplementedUserServiceServer) GetMyProfileStats(context.Context, *GetMyProfileStatsRequest) (*GetMyProfileStatsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetMyProfileStats not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetMyProfileStats not implemented")
}
func (UnimplementedUserServiceServer) BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method BatchGetUsers not implemented")
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUsers not implemented")
}
func (UnimplementedUserServiceServer) ListUserIDs(context.Context, *ListUserIDsRequest) (*ListUserIDsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListUserIDs not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListUserIDs not implemented")
}
func (UnimplementedUserServiceServer) GetUserMicLifetimeStats(context.Context, *GetUserMicLifetimeStatsRequest) (*GetUserMicLifetimeStatsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetUserMicLifetimeStats not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetUserMicLifetimeStats not implemented")
}
func (UnimplementedUserServiceServer) UpdateUserProfile(context.Context, *UpdateUserProfileRequest) (*UpdateUserProfileResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateUserProfile not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserProfile not implemented")
}
func (UnimplementedUserServiceServer) UpdateUserProfileBackground(context.Context, *UpdateUserProfileBackgroundRequest) (*UpdateUserProfileBackgroundResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateUserProfileBackground not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserProfileBackground not implemented")
}
func (UnimplementedUserServiceServer) ChangeUserCountry(context.Context, *ChangeUserCountryRequest) (*ChangeUserCountryResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ChangeUserCountry not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ChangeUserCountry not implemented")
}
func (UnimplementedUserServiceServer) AdminChangeUserCountry(context.Context, *AdminChangeUserCountryRequest) (*ChangeUserCountryResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminChangeUserCountry not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminChangeUserCountry not implemented")
}
func (UnimplementedUserServiceServer) SetUserStatus(context.Context, *SetUserStatusRequest) (*SetUserStatusResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetUserStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetUserStatus not implemented")
}
func (UnimplementedUserServiceServer) CreateManagerUserBlock(context.Context, *CreateManagerUserBlockRequest) (*CreateManagerUserBlockResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateManagerUserBlock not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateManagerUserBlock not implemented")
}
func (UnimplementedUserServiceServer) ListManagerUserBlocks(context.Context, *ListManagerUserBlocksRequest) (*ListManagerUserBlocksResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListManagerUserBlocks not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListManagerUserBlocks not implemented")
}
func (UnimplementedUserServiceServer) UnblockManagerUser(context.Context, *UnblockManagerUserRequest) (*UnblockManagerUserResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UnblockManagerUser not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UnblockManagerUser not implemented")
}
func (UnimplementedUserServiceServer) CompleteOnboarding(context.Context, *CompleteOnboardingRequest) (*CompleteOnboardingResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CompleteOnboarding not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CompleteOnboarding not implemented")
}
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
func (UnimplementedUserServiceServer) testEmbeddedByValue() {}
@ -320,7 +320,7 @@ type UnsafeUserServiceServer interface {
}
func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
// If the following call panics, it indicates UnimplementedUserServiceServer was
// If the following call pancis, it indicates UnimplementedUserServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -873,37 +873,37 @@ type UserSocialServiceServer interface {
type UnimplementedUserSocialServiceServer struct{}
func (UnimplementedUserSocialServiceServer) RecordProfileVisit(context.Context, *RecordProfileVisitRequest) (*RecordProfileVisitResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RecordProfileVisit not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RecordProfileVisit not implemented")
}
func (UnimplementedUserSocialServiceServer) ListProfileVisitors(context.Context, *ListProfileVisitorsRequest) (*ListProfileVisitorsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListProfileVisitors not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListProfileVisitors not implemented")
}
func (UnimplementedUserSocialServiceServer) FollowUser(context.Context, *FollowUserRequest) (*FollowUserResponse, error) {
return nil, status.Error(codes.Unimplemented, "method FollowUser not implemented")
return nil, status.Errorf(codes.Unimplemented, "method FollowUser not implemented")
}
func (UnimplementedUserSocialServiceServer) UnfollowUser(context.Context, *UnfollowUserRequest) (*UnfollowUserResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UnfollowUser not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UnfollowUser not implemented")
}
func (UnimplementedUserSocialServiceServer) ListFollowing(context.Context, *ListFollowingRequest) (*ListFollowingResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListFollowing not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListFollowing not implemented")
}
func (UnimplementedUserSocialServiceServer) ApplyFriend(context.Context, *ApplyFriendRequest) (*ApplyFriendResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ApplyFriend not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ApplyFriend not implemented")
}
func (UnimplementedUserSocialServiceServer) AcceptFriendApplication(context.Context, *AcceptFriendApplicationRequest) (*AcceptFriendApplicationResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AcceptFriendApplication not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AcceptFriendApplication not implemented")
}
func (UnimplementedUserSocialServiceServer) DeleteFriend(context.Context, *DeleteFriendRequest) (*DeleteFriendResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteFriend not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DeleteFriend not implemented")
}
func (UnimplementedUserSocialServiceServer) ListFriends(context.Context, *ListFriendsRequest) (*ListFriendsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListFriends not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListFriends not implemented")
}
func (UnimplementedUserSocialServiceServer) ListFriendApplications(context.Context, *ListFriendApplicationsRequest) (*ListFriendApplicationsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListFriendApplications not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListFriendApplications not implemented")
}
func (UnimplementedUserSocialServiceServer) SubmitReport(context.Context, *SubmitReportRequest) (*SubmitReportResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SubmitReport not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SubmitReport not implemented")
}
func (UnimplementedUserSocialServiceServer) mustEmbedUnimplementedUserSocialServiceServer() {}
func (UnimplementedUserSocialServiceServer) testEmbeddedByValue() {}
@ -916,7 +916,7 @@ type UnsafeUserSocialServiceServer interface {
}
func RegisterUserSocialServiceServer(s grpc.ServiceRegistrar, srv UserSocialServiceServer) {
// If the following call panics, it indicates UnimplementedUserSocialServiceServer was
// If the following call pancis, it indicates UnimplementedUserSocialServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1320,28 +1320,28 @@ type UserCPServiceServer interface {
type UnimplementedUserCPServiceServer struct{}
func (UnimplementedUserCPServiceServer) ListCPApplications(context.Context, *ListCPApplicationsRequest) (*ListCPApplicationsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListCPApplications not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListCPApplications not implemented")
}
func (UnimplementedUserCPServiceServer) AcceptCPApplication(context.Context, *AcceptCPApplicationRequest) (*AcceptCPApplicationResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AcceptCPApplication not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AcceptCPApplication not implemented")
}
func (UnimplementedUserCPServiceServer) RejectCPApplication(context.Context, *RejectCPApplicationRequest) (*RejectCPApplicationResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RejectCPApplication not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RejectCPApplication not implemented")
}
func (UnimplementedUserCPServiceServer) ListCPRelationships(context.Context, *ListCPRelationshipsRequest) (*ListCPRelationshipsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListCPRelationships not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListCPRelationships not implemented")
}
func (UnimplementedUserCPServiceServer) ListCPIntimacyLeaderboard(context.Context, *ListCPIntimacyLeaderboardRequest) (*ListCPIntimacyLeaderboardResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListCPIntimacyLeaderboard not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListCPIntimacyLeaderboard not implemented")
}
func (UnimplementedUserCPServiceServer) PrepareBreakCPRelationship(context.Context, *PrepareBreakCPRelationshipRequest) (*PrepareBreakCPRelationshipResponse, error) {
return nil, status.Error(codes.Unimplemented, "method PrepareBreakCPRelationship not implemented")
return nil, status.Errorf(codes.Unimplemented, "method PrepareBreakCPRelationship not implemented")
}
func (UnimplementedUserCPServiceServer) ConfirmBreakCPRelationship(context.Context, *ConfirmBreakCPRelationshipRequest) (*ConfirmBreakCPRelationshipResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ConfirmBreakCPRelationship not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ConfirmBreakCPRelationship not implemented")
}
func (UnimplementedUserCPServiceServer) CancelBreakCPRelationship(context.Context, *CancelBreakCPRelationshipRequest) (*CancelBreakCPRelationshipResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CancelBreakCPRelationship not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CancelBreakCPRelationship not implemented")
}
func (UnimplementedUserCPServiceServer) mustEmbedUnimplementedUserCPServiceServer() {}
func (UnimplementedUserCPServiceServer) testEmbeddedByValue() {}
@ -1354,7 +1354,7 @@ type UnsafeUserCPServiceServer interface {
}
func RegisterUserCPServiceServer(s grpc.ServiceRegistrar, srv UserCPServiceServer) {
// If the following call panics, it indicates UnimplementedUserCPServiceServer was
// If the following call pancis, it indicates UnimplementedUserCPServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1614,10 +1614,10 @@ type UserCPInternalServiceServer interface {
type UnimplementedUserCPInternalServiceServer struct{}
func (UnimplementedUserCPInternalServiceServer) ConsumeRoomGiftCPEvent(context.Context, *ConsumeRoomGiftCPEventRequest) (*ConsumeRoomGiftCPEventResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ConsumeRoomGiftCPEvent not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ConsumeRoomGiftCPEvent not implemented")
}
func (UnimplementedUserCPInternalServiceServer) ListCPWeeklyRankEntries(context.Context, *ListCPWeeklyRankEntriesRequest) (*ListCPWeeklyRankEntriesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListCPWeeklyRankEntries not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListCPWeeklyRankEntries not implemented")
}
func (UnimplementedUserCPInternalServiceServer) mustEmbedUnimplementedUserCPInternalServiceServer() {}
func (UnimplementedUserCPInternalServiceServer) testEmbeddedByValue() {}
@ -1630,7 +1630,7 @@ type UnsafeUserCPInternalServiceServer interface {
}
func RegisterUserCPInternalServiceServer(s grpc.ServiceRegistrar, srv UserCPInternalServiceServer) {
// If the following call panics, it indicates UnimplementedUserCPInternalServiceServer was
// If the following call pancis, it indicates UnimplementedUserCPInternalServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1797,19 +1797,19 @@ type UserCronServiceServer interface {
type UnimplementedUserCronServiceServer struct{}
func (UnimplementedUserCronServiceServer) ProcessLoginIPRiskBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ProcessLoginIPRiskBatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ProcessLoginIPRiskBatch not implemented")
}
func (UnimplementedUserCronServiceServer) ProcessRegionRebuildBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ProcessRegionRebuildBatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ProcessRegionRebuildBatch not implemented")
}
func (UnimplementedUserCronServiceServer) CompensateMicOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CompensateMicOpenSessions not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CompensateMicOpenSessions not implemented")
}
func (UnimplementedUserCronServiceServer) ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ExpireManagerUserBlocks not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ExpireManagerUserBlocks not implemented")
}
func (UnimplementedUserCronServiceServer) RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RefreshCPIntimacyLeaderboard not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RefreshCPIntimacyLeaderboard not implemented")
}
func (UnimplementedUserCronServiceServer) mustEmbedUnimplementedUserCronServiceServer() {}
func (UnimplementedUserCronServiceServer) testEmbeddedByValue() {}
@ -1822,7 +1822,7 @@ type UnsafeUserCronServiceServer interface {
}
func RegisterUserCronServiceServer(s grpc.ServiceRegistrar, srv UserCronServiceServer) {
// If the following call panics, it indicates UnimplementedUserCronServiceServer was
// If the following call pancis, it indicates UnimplementedUserCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2016,10 +2016,10 @@ type UserDeviceServiceServer interface {
type UnimplementedUserDeviceServiceServer struct{}
func (UnimplementedUserDeviceServiceServer) BindPushToken(context.Context, *BindPushTokenRequest) (*BindPushTokenResponse, error) {
return nil, status.Error(codes.Unimplemented, "method BindPushToken not implemented")
return nil, status.Errorf(codes.Unimplemented, "method BindPushToken not implemented")
}
func (UnimplementedUserDeviceServiceServer) DeletePushToken(context.Context, *DeletePushTokenRequest) (*DeletePushTokenResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeletePushToken not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DeletePushToken not implemented")
}
func (UnimplementedUserDeviceServiceServer) mustEmbedUnimplementedUserDeviceServiceServer() {}
func (UnimplementedUserDeviceServiceServer) testEmbeddedByValue() {}
@ -2032,7 +2032,7 @@ type UnsafeUserDeviceServiceServer interface {
}
func RegisterUserDeviceServiceServer(s grpc.ServiceRegistrar, srv UserDeviceServiceServer) {
// If the following call panics, it indicates UnimplementedUserDeviceServiceServer was
// If the following call pancis, it indicates UnimplementedUserDeviceServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2147,7 +2147,7 @@ type AppRegistryServiceServer interface {
type UnimplementedAppRegistryServiceServer struct{}
func (UnimplementedAppRegistryServiceServer) ResolveApp(context.Context, *ResolveAppRequest) (*ResolveAppResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ResolveApp not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ResolveApp not implemented")
}
func (UnimplementedAppRegistryServiceServer) mustEmbedUnimplementedAppRegistryServiceServer() {}
func (UnimplementedAppRegistryServiceServer) testEmbeddedByValue() {}
@ -2160,7 +2160,7 @@ type UnsafeAppRegistryServiceServer interface {
}
func RegisterAppRegistryServiceServer(s grpc.ServiceRegistrar, srv AppRegistryServiceServer) {
// If the following call panics, it indicates UnimplementedAppRegistryServiceServer was
// If the following call pancis, it indicates UnimplementedAppRegistryServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2268,10 +2268,10 @@ type CountryAdminServiceServer interface {
type UnimplementedCountryAdminServiceServer struct{}
func (UnimplementedCountryAdminServiceServer) ListCountries(context.Context, *ListCountriesRequest) (*ListCountriesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListCountries not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListCountries not implemented")
}
func (UnimplementedCountryAdminServiceServer) UpdateCountry(context.Context, *UpdateCountryRequest) (*CountryResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateCountry not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateCountry not implemented")
}
func (UnimplementedCountryAdminServiceServer) mustEmbedUnimplementedCountryAdminServiceServer() {}
func (UnimplementedCountryAdminServiceServer) testEmbeddedByValue() {}
@ -2284,7 +2284,7 @@ type UnsafeCountryAdminServiceServer interface {
}
func RegisterCountryAdminServiceServer(s grpc.ServiceRegistrar, srv CountryAdminServiceServer) {
// If the following call panics, it indicates UnimplementedCountryAdminServiceServer was
// If the following call pancis, it indicates UnimplementedCountryAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2412,10 +2412,10 @@ type CountryQueryServiceServer interface {
type UnimplementedCountryQueryServiceServer struct{}
func (UnimplementedCountryQueryServiceServer) ListRegistrationCountries(context.Context, *ListRegistrationCountriesRequest) (*ListRegistrationCountriesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRegistrationCountries not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRegistrationCountries not implemented")
}
func (UnimplementedCountryQueryServiceServer) ListLoginRiskBlockedCountries(context.Context, *ListLoginRiskBlockedCountriesRequest) (*ListLoginRiskBlockedCountriesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListLoginRiskBlockedCountries not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListLoginRiskBlockedCountries not implemented")
}
func (UnimplementedCountryQueryServiceServer) mustEmbedUnimplementedCountryQueryServiceServer() {}
func (UnimplementedCountryQueryServiceServer) testEmbeddedByValue() {}
@ -2428,7 +2428,7 @@ type UnsafeCountryQueryServiceServer interface {
}
func RegisterCountryQueryServiceServer(s grpc.ServiceRegistrar, srv CountryQueryServiceServer) {
// If the following call panics, it indicates UnimplementedCountryQueryServiceServer was
// If the following call pancis, it indicates UnimplementedCountryQueryServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2584,16 +2584,16 @@ type RegionAdminServiceServer interface {
type UnimplementedRegionAdminServiceServer struct{}
func (UnimplementedRegionAdminServiceServer) ListRegions(context.Context, *ListRegionsRequest) (*ListRegionsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRegions not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRegions not implemented")
}
func (UnimplementedRegionAdminServiceServer) GetRegion(context.Context, *GetRegionRequest) (*RegionResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRegion not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetRegion not implemented")
}
func (UnimplementedRegionAdminServiceServer) UpdateRegion(context.Context, *UpdateRegionRequest) (*RegionResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateRegion not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegion not implemented")
}
func (UnimplementedRegionAdminServiceServer) ReplaceRegionCountries(context.Context, *ReplaceRegionCountriesRequest) (*RegionResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ReplaceRegionCountries not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ReplaceRegionCountries not implemented")
}
func (UnimplementedRegionAdminServiceServer) mustEmbedUnimplementedRegionAdminServiceServer() {}
func (UnimplementedRegionAdminServiceServer) testEmbeddedByValue() {}
@ -2606,7 +2606,7 @@ type UnsafeRegionAdminServiceServer interface {
}
func RegisterRegionAdminServiceServer(s grpc.ServiceRegistrar, srv RegionAdminServiceServer) {
// If the following call panics, it indicates UnimplementedRegionAdminServiceServer was
// If the following call pancis, it indicates UnimplementedRegionAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2843,25 +2843,25 @@ type UserIdentityServiceServer interface {
type UnimplementedUserIdentityServiceServer struct{}
func (UnimplementedUserIdentityServiceServer) GetUserIdentity(context.Context, *GetUserIdentityRequest) (*GetUserIdentityResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetUserIdentity not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetUserIdentity not implemented")
}
func (UnimplementedUserIdentityServiceServer) ResolveDisplayUserID(context.Context, *ResolveDisplayUserIDRequest) (*ResolveDisplayUserIDResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ResolveDisplayUserID not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ResolveDisplayUserID not implemented")
}
func (UnimplementedUserIdentityServiceServer) ChangeDisplayUserID(context.Context, *ChangeDisplayUserIDRequest) (*ChangeDisplayUserIDResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ChangeDisplayUserID not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ChangeDisplayUserID not implemented")
}
func (UnimplementedUserIdentityServiceServer) ApplyPrettyDisplayUserID(context.Context, *ApplyPrettyDisplayUserIDRequest) (*ApplyPrettyDisplayUserIDResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ApplyPrettyDisplayUserID not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ApplyPrettyDisplayUserID not implemented")
}
func (UnimplementedUserIdentityServiceServer) ListAvailablePrettyDisplayIDs(context.Context, *ListAvailablePrettyDisplayIDsRequest) (*ListAvailablePrettyDisplayIDsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListAvailablePrettyDisplayIDs not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListAvailablePrettyDisplayIDs not implemented")
}
func (UnimplementedUserIdentityServiceServer) ApplyPrettyDisplayIDFromPool(context.Context, *ApplyPrettyDisplayIDFromPoolRequest) (*ApplyPrettyDisplayIDFromPoolResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ApplyPrettyDisplayIDFromPool not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ApplyPrettyDisplayIDFromPool not implemented")
}
func (UnimplementedUserIdentityServiceServer) ExpirePrettyDisplayUserID(context.Context, *ExpirePrettyDisplayUserIDRequest) (*ExpirePrettyDisplayUserIDResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ExpirePrettyDisplayUserID not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ExpirePrettyDisplayUserID not implemented")
}
func (UnimplementedUserIdentityServiceServer) mustEmbedUnimplementedUserIdentityServiceServer() {}
func (UnimplementedUserIdentityServiceServer) testEmbeddedByValue() {}
@ -2874,7 +2874,7 @@ type UnsafeUserIdentityServiceServer interface {
}
func RegisterUserIdentityServiceServer(s grpc.ServiceRegistrar, srv UserIdentityServiceServer) {
// If the following call panics, it indicates UnimplementedUserIdentityServiceServer was
// If the following call pancis, it indicates UnimplementedUserIdentityServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -3177,25 +3177,25 @@ type UserPrettyDisplayIDAdminServiceServer interface {
type UnimplementedUserPrettyDisplayIDAdminServiceServer struct{}
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) ListPrettyDisplayIDPools(context.Context, *ListPrettyDisplayIDPoolsRequest) (*ListPrettyDisplayIDPoolsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListPrettyDisplayIDPools not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListPrettyDisplayIDPools not implemented")
}
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) CreatePrettyDisplayIDPool(context.Context, *CreatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreatePrettyDisplayIDPool not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreatePrettyDisplayIDPool not implemented")
}
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) UpdatePrettyDisplayIDPool(context.Context, *UpdatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdatePrettyDisplayIDPool not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdatePrettyDisplayIDPool not implemented")
}
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) GeneratePrettyDisplayIDs(context.Context, *GeneratePrettyDisplayIDsRequest) (*GeneratePrettyDisplayIDsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GeneratePrettyDisplayIDs not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GeneratePrettyDisplayIDs not implemented")
}
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) ListPrettyDisplayIDs(context.Context, *ListPrettyDisplayIDsRequest) (*ListPrettyDisplayIDsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListPrettyDisplayIDs not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListPrettyDisplayIDs not implemented")
}
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) SetPrettyDisplayIDStatus(context.Context, *SetPrettyDisplayIDStatusRequest) (*PrettyDisplayIDResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetPrettyDisplayIDStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetPrettyDisplayIDStatus not implemented")
}
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) AdminGrantPrettyDisplayID(context.Context, *AdminGrantPrettyDisplayIDRequest) (*AdminGrantPrettyDisplayIDResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminGrantPrettyDisplayID not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminGrantPrettyDisplayID not implemented")
}
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) mustEmbedUnimplementedUserPrettyDisplayIDAdminServiceServer() {
}
@ -3209,7 +3209,7 @@ type UnsafeUserPrettyDisplayIDAdminServiceServer interface {
}
func RegisterUserPrettyDisplayIDAdminServiceServer(s grpc.ServiceRegistrar, srv UserPrettyDisplayIDAdminServiceServer) {
// If the following call panics, it indicates UnimplementedUserPrettyDisplayIDAdminServiceServer was
// If the following call pancis, it indicates UnimplementedUserPrettyDisplayIDAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1471,6 +1471,22 @@ message DebitCPBreakupFeeResponse {
AssetBalance balance = 4;
}
message DebitWheelDrawRequest {
string command_id = 1;
string app_code = 2;
int64 user_id = 3;
string wheel_id = 4;
int32 draw_count = 5;
int64 amount = 6;
}
message DebitWheelDrawResponse {
string transaction_id = 1;
int64 coin_spent = 2;
int64 coin_balance_after = 3;
AssetBalance balance = 4;
}
message GrantVipRequest {
string command_id = 1;
string app_code = 2;
@ -1563,6 +1579,27 @@ message CreditLuckyGiftRewardResponse {
int64 granted_at_ms = 4;
}
// CreditWheelRewardRequest activity-service outbox
message CreditWheelRewardRequest {
string command_id = 1;
string app_code = 2;
int64 target_user_id = 3;
int64 amount = 4;
string draw_id = 5;
string wheel_id = 6;
string selected_tier_id = 7;
string reason = 8;
int64 visible_region_id = 9;
}
// CreditWheelRewardResponse COIN
message CreditWheelRewardResponse {
string transaction_id = 1;
AssetBalance balance = 2;
int64 amount = 3;
int64 granted_at_ms = 4;
}
// CreditRoomTurnoverRewardRequest activity-service
message CreditRoomTurnoverRewardRequest {
string command_id = 1;
@ -1928,11 +1965,13 @@ service WalletService {
rpc GetMyVip(GetMyVipRequest) returns (GetMyVipResponse);
rpc PurchaseVip(PurchaseVipRequest) returns (PurchaseVipResponse);
rpc DebitCPBreakupFee(DebitCPBreakupFeeRequest) returns (DebitCPBreakupFeeResponse);
rpc DebitWheelDraw(DebitWheelDrawRequest) returns (DebitWheelDrawResponse);
rpc GrantVip(GrantVipRequest) returns (GrantVipResponse);
rpc ListAdminVipLevels(ListAdminVipLevelsRequest) returns (ListAdminVipLevelsResponse);
rpc UpdateAdminVipLevels(UpdateAdminVipLevelsRequest) returns (UpdateAdminVipLevelsResponse);
rpc CreditTaskReward(CreditTaskRewardRequest) returns (CreditTaskRewardResponse);
rpc CreditLuckyGiftReward(CreditLuckyGiftRewardRequest) returns (CreditLuckyGiftRewardResponse);
rpc CreditWheelReward(CreditWheelRewardRequest) returns (CreditWheelRewardResponse);
rpc CreditRoomTurnoverReward(CreditRoomTurnoverRewardRequest) returns (CreditRoomTurnoverRewardResponse);
rpc CreditInviteActivityReward(CreditInviteActivityRewardRequest) returns (CreditInviteActivityRewardResponse);
rpc CreditAgencyOpeningReward(CreditAgencyOpeningRewardRequest) returns (CreditAgencyOpeningRewardResponse);

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc-gen-go-grpc v1.5.1
// - protoc v7.35.0
// source: proto/wallet/v1/wallet.proto
@ -93,13 +93,13 @@ type WalletCronServiceServer interface {
type UnimplementedWalletCronServiceServer struct{}
func (UnimplementedWalletCronServiceServer) ProcessHostSalaryDailySettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ProcessHostSalaryDailySettlementBatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryDailySettlementBatch not implemented")
}
func (UnimplementedWalletCronServiceServer) ProcessHostSalaryHalfMonthSettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ProcessHostSalaryHalfMonthSettlementBatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryHalfMonthSettlementBatch not implemented")
}
func (UnimplementedWalletCronServiceServer) ProcessHostSalaryMonthEndBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ProcessHostSalaryMonthEndBatch not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryMonthEndBatch not implemented")
}
func (UnimplementedWalletCronServiceServer) mustEmbedUnimplementedWalletCronServiceServer() {}
func (UnimplementedWalletCronServiceServer) testEmbeddedByValue() {}
@ -112,7 +112,7 @@ type UnsafeWalletCronServiceServer interface {
}
func RegisterWalletCronServiceServer(s grpc.ServiceRegistrar, srv WalletCronServiceServer) {
// If the following call panics, it indicates UnimplementedWalletCronServiceServer was
// If the following call pancis, it indicates UnimplementedWalletCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -265,11 +265,13 @@ const (
WalletService_GetMyVip_FullMethodName = "/hyapp.wallet.v1.WalletService/GetMyVip"
WalletService_PurchaseVip_FullMethodName = "/hyapp.wallet.v1.WalletService/PurchaseVip"
WalletService_DebitCPBreakupFee_FullMethodName = "/hyapp.wallet.v1.WalletService/DebitCPBreakupFee"
WalletService_DebitWheelDraw_FullMethodName = "/hyapp.wallet.v1.WalletService/DebitWheelDraw"
WalletService_GrantVip_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantVip"
WalletService_ListAdminVipLevels_FullMethodName = "/hyapp.wallet.v1.WalletService/ListAdminVipLevels"
WalletService_UpdateAdminVipLevels_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateAdminVipLevels"
WalletService_CreditTaskReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditTaskReward"
WalletService_CreditLuckyGiftReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditLuckyGiftReward"
WalletService_CreditWheelReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditWheelReward"
WalletService_CreditRoomTurnoverReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditRoomTurnoverReward"
WalletService_CreditInviteActivityReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditInviteActivityReward"
WalletService_CreditAgencyOpeningReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditAgencyOpeningReward"
@ -354,11 +356,13 @@ type WalletServiceClient interface {
GetMyVip(ctx context.Context, in *GetMyVipRequest, opts ...grpc.CallOption) (*GetMyVipResponse, error)
PurchaseVip(ctx context.Context, in *PurchaseVipRequest, opts ...grpc.CallOption) (*PurchaseVipResponse, error)
DebitCPBreakupFee(ctx context.Context, in *DebitCPBreakupFeeRequest, opts ...grpc.CallOption) (*DebitCPBreakupFeeResponse, error)
DebitWheelDraw(ctx context.Context, in *DebitWheelDrawRequest, opts ...grpc.CallOption) (*DebitWheelDrawResponse, error)
GrantVip(ctx context.Context, in *GrantVipRequest, opts ...grpc.CallOption) (*GrantVipResponse, error)
ListAdminVipLevels(ctx context.Context, in *ListAdminVipLevelsRequest, opts ...grpc.CallOption) (*ListAdminVipLevelsResponse, error)
UpdateAdminVipLevels(ctx context.Context, in *UpdateAdminVipLevelsRequest, opts ...grpc.CallOption) (*UpdateAdminVipLevelsResponse, error)
CreditTaskReward(ctx context.Context, in *CreditTaskRewardRequest, opts ...grpc.CallOption) (*CreditTaskRewardResponse, error)
CreditLuckyGiftReward(ctx context.Context, in *CreditLuckyGiftRewardRequest, opts ...grpc.CallOption) (*CreditLuckyGiftRewardResponse, error)
CreditWheelReward(ctx context.Context, in *CreditWheelRewardRequest, opts ...grpc.CallOption) (*CreditWheelRewardResponse, error)
CreditRoomTurnoverReward(ctx context.Context, in *CreditRoomTurnoverRewardRequest, opts ...grpc.CallOption) (*CreditRoomTurnoverRewardResponse, error)
CreditInviteActivityReward(ctx context.Context, in *CreditInviteActivityRewardRequest, opts ...grpc.CallOption) (*CreditInviteActivityRewardResponse, error)
CreditAgencyOpeningReward(ctx context.Context, in *CreditAgencyOpeningRewardRequest, opts ...grpc.CallOption) (*CreditAgencyOpeningRewardResponse, error)
@ -1021,6 +1025,16 @@ func (c *walletServiceClient) DebitCPBreakupFee(ctx context.Context, in *DebitCP
return out, nil
}
func (c *walletServiceClient) DebitWheelDraw(ctx context.Context, in *DebitWheelDrawRequest, opts ...grpc.CallOption) (*DebitWheelDrawResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DebitWheelDrawResponse)
err := c.cc.Invoke(ctx, WalletService_DebitWheelDraw_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) GrantVip(ctx context.Context, in *GrantVipRequest, opts ...grpc.CallOption) (*GrantVipResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GrantVipResponse)
@ -1071,6 +1085,16 @@ func (c *walletServiceClient) CreditLuckyGiftReward(ctx context.Context, in *Cre
return out, nil
}
func (c *walletServiceClient) CreditWheelReward(ctx context.Context, in *CreditWheelRewardRequest, opts ...grpc.CallOption) (*CreditWheelRewardResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CreditWheelRewardResponse)
err := c.cc.Invoke(ctx, WalletService_CreditWheelReward_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) CreditRoomTurnoverReward(ctx context.Context, in *CreditRoomTurnoverRewardRequest, opts ...grpc.CallOption) (*CreditRoomTurnoverRewardResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CreditRoomTurnoverRewardResponse)
@ -1261,11 +1285,13 @@ type WalletServiceServer interface {
GetMyVip(context.Context, *GetMyVipRequest) (*GetMyVipResponse, error)
PurchaseVip(context.Context, *PurchaseVipRequest) (*PurchaseVipResponse, error)
DebitCPBreakupFee(context.Context, *DebitCPBreakupFeeRequest) (*DebitCPBreakupFeeResponse, error)
DebitWheelDraw(context.Context, *DebitWheelDrawRequest) (*DebitWheelDrawResponse, error)
GrantVip(context.Context, *GrantVipRequest) (*GrantVipResponse, error)
ListAdminVipLevels(context.Context, *ListAdminVipLevelsRequest) (*ListAdminVipLevelsResponse, error)
UpdateAdminVipLevels(context.Context, *UpdateAdminVipLevelsRequest) (*UpdateAdminVipLevelsResponse, error)
CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error)
CreditLuckyGiftReward(context.Context, *CreditLuckyGiftRewardRequest) (*CreditLuckyGiftRewardResponse, error)
CreditWheelReward(context.Context, *CreditWheelRewardRequest) (*CreditWheelRewardResponse, error)
CreditRoomTurnoverReward(context.Context, *CreditRoomTurnoverRewardRequest) (*CreditRoomTurnoverRewardResponse, error)
CreditInviteActivityReward(context.Context, *CreditInviteActivityRewardRequest) (*CreditInviteActivityRewardResponse, error)
CreditAgencyOpeningReward(context.Context, *CreditAgencyOpeningRewardRequest) (*CreditAgencyOpeningRewardResponse, error)
@ -1289,247 +1315,253 @@ type WalletServiceServer interface {
type UnimplementedWalletServiceServer struct{}
func (UnimplementedWalletServiceServer) DebitGift(context.Context, *DebitGiftRequest) (*DebitGiftResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DebitGift not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DebitGift not implemented")
}
func (UnimplementedWalletServiceServer) BatchDebitGift(context.Context, *BatchDebitGiftRequest) (*BatchDebitGiftResponse, error) {
return nil, status.Error(codes.Unimplemented, "method BatchDebitGift not implemented")
return nil, status.Errorf(codes.Unimplemented, "method BatchDebitGift not implemented")
}
func (UnimplementedWalletServiceServer) DebitRobotGift(context.Context, *DebitRobotGiftRequest) (*DebitGiftResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DebitRobotGift not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DebitRobotGift not implemented")
}
func (UnimplementedWalletServiceServer) GetBalances(context.Context, *GetBalancesRequest) (*GetBalancesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetBalances not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetBalances not implemented")
}
func (UnimplementedWalletServiceServer) GetActiveHostSalaryPolicy(context.Context, *GetActiveHostSalaryPolicyRequest) (*GetActiveHostSalaryPolicyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetActiveHostSalaryPolicy not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetActiveHostSalaryPolicy not implemented")
}
func (UnimplementedWalletServiceServer) GetHostSalaryProgress(context.Context, *GetHostSalaryProgressRequest) (*GetHostSalaryProgressResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetHostSalaryProgress not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetHostSalaryProgress not implemented")
}
func (UnimplementedWalletServiceServer) AdminCreditAsset(context.Context, *AdminCreditAssetRequest) (*AdminCreditAssetResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminCreditAsset not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminCreditAsset not implemented")
}
func (UnimplementedWalletServiceServer) AdminCreditCoinSellerStock(context.Context, *AdminCreditCoinSellerStockRequest) (*AdminCreditCoinSellerStockResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AdminCreditCoinSellerStock not implemented")
return nil, status.Errorf(codes.Unimplemented, "method AdminCreditCoinSellerStock not implemented")
}
func (UnimplementedWalletServiceServer) TransferCoinFromSeller(context.Context, *TransferCoinFromSellerRequest) (*TransferCoinFromSellerResponse, error) {
return nil, status.Error(codes.Unimplemented, "method TransferCoinFromSeller not implemented")
return nil, status.Errorf(codes.Unimplemented, "method TransferCoinFromSeller not implemented")
}
func (UnimplementedWalletServiceServer) ListCoinSellerSalaryExchangeRateTiers(context.Context, *ListCoinSellerSalaryExchangeRateTiersRequest) (*ListCoinSellerSalaryExchangeRateTiersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListCoinSellerSalaryExchangeRateTiers not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListCoinSellerSalaryExchangeRateTiers not implemented")
}
func (UnimplementedWalletServiceServer) ExchangeSalaryToCoin(context.Context, *ExchangeSalaryToCoinRequest) (*ExchangeSalaryToCoinResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ExchangeSalaryToCoin not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ExchangeSalaryToCoin not implemented")
}
func (UnimplementedWalletServiceServer) TransferSalaryToCoinSeller(context.Context, *TransferSalaryToCoinSellerRequest) (*TransferSalaryToCoinSellerResponse, error) {
return nil, status.Error(codes.Unimplemented, "method TransferSalaryToCoinSeller not implemented")
return nil, status.Errorf(codes.Unimplemented, "method TransferSalaryToCoinSeller not implemented")
}
func (UnimplementedWalletServiceServer) ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListResources not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListResources not implemented")
}
func (UnimplementedWalletServiceServer) GetResource(context.Context, *GetResourceRequest) (*GetResourceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetResource not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetResource not implemented")
}
func (UnimplementedWalletServiceServer) CreateResource(context.Context, *CreateResourceRequest) (*ResourceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateResource not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateResource not implemented")
}
func (UnimplementedWalletServiceServer) UpdateResource(context.Context, *UpdateResourceRequest) (*ResourceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateResource not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateResource not implemented")
}
func (UnimplementedWalletServiceServer) SetResourceStatus(context.Context, *SetResourceStatusRequest) (*ResourceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetResourceStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetResourceStatus not implemented")
}
func (UnimplementedWalletServiceServer) ListResourceGroups(context.Context, *ListResourceGroupsRequest) (*ListResourceGroupsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListResourceGroups not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListResourceGroups not implemented")
}
func (UnimplementedWalletServiceServer) GetResourceGroup(context.Context, *GetResourceGroupRequest) (*GetResourceGroupResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetResourceGroup not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetResourceGroup not implemented")
}
func (UnimplementedWalletServiceServer) CreateResourceGroup(context.Context, *CreateResourceGroupRequest) (*ResourceGroupResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateResourceGroup not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateResourceGroup not implemented")
}
func (UnimplementedWalletServiceServer) UpdateResourceGroup(context.Context, *UpdateResourceGroupRequest) (*ResourceGroupResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateResourceGroup not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateResourceGroup not implemented")
}
func (UnimplementedWalletServiceServer) SetResourceGroupStatus(context.Context, *SetResourceGroupStatusRequest) (*ResourceGroupResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetResourceGroupStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetResourceGroupStatus not implemented")
}
func (UnimplementedWalletServiceServer) ListGiftConfigs(context.Context, *ListGiftConfigsRequest) (*ListGiftConfigsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListGiftConfigs not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListGiftConfigs not implemented")
}
func (UnimplementedWalletServiceServer) ListGiftTypeConfigs(context.Context, *ListGiftTypeConfigsRequest) (*ListGiftTypeConfigsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListGiftTypeConfigs not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListGiftTypeConfigs not implemented")
}
func (UnimplementedWalletServiceServer) CreateGiftConfig(context.Context, *CreateGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateGiftConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateGiftConfig not implemented")
}
func (UnimplementedWalletServiceServer) UpdateGiftConfig(context.Context, *UpdateGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateGiftConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateGiftConfig not implemented")
}
func (UnimplementedWalletServiceServer) SetGiftConfigStatus(context.Context, *SetGiftConfigStatusRequest) (*GiftConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetGiftConfigStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetGiftConfigStatus not implemented")
}
func (UnimplementedWalletServiceServer) DeleteGiftConfig(context.Context, *DeleteGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteGiftConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DeleteGiftConfig not implemented")
}
func (UnimplementedWalletServiceServer) UpsertGiftTypeConfig(context.Context, *UpsertGiftTypeConfigRequest) (*GiftTypeConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpsertGiftTypeConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpsertGiftTypeConfig not implemented")
}
func (UnimplementedWalletServiceServer) GrantResource(context.Context, *GrantResourceRequest) (*ResourceGrantResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GrantResource not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GrantResource not implemented")
}
func (UnimplementedWalletServiceServer) GrantResourceGroup(context.Context, *GrantResourceGroupRequest) (*ResourceGrantResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GrantResourceGroup not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GrantResourceGroup not implemented")
}
func (UnimplementedWalletServiceServer) ListUserResources(context.Context, *ListUserResourcesRequest) (*ListUserResourcesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListUserResources not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListUserResources not implemented")
}
func (UnimplementedWalletServiceServer) EquipUserResource(context.Context, *EquipUserResourceRequest) (*EquipUserResourceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method EquipUserResource not implemented")
return nil, status.Errorf(codes.Unimplemented, "method EquipUserResource not implemented")
}
func (UnimplementedWalletServiceServer) UnequipUserResource(context.Context, *UnequipUserResourceRequest) (*UnequipUserResourceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UnequipUserResource not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UnequipUserResource not implemented")
}
func (UnimplementedWalletServiceServer) BatchGetUserEquippedResources(context.Context, *BatchGetUserEquippedResourcesRequest) (*BatchGetUserEquippedResourcesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method BatchGetUserEquippedResources not implemented")
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUserEquippedResources not implemented")
}
func (UnimplementedWalletServiceServer) ListResourceGrants(context.Context, *ListResourceGrantsRequest) (*ListResourceGrantsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListResourceGrants not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListResourceGrants not implemented")
}
func (UnimplementedWalletServiceServer) ListResourceShopItems(context.Context, *ListResourceShopItemsRequest) (*ListResourceShopItemsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListResourceShopItems not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListResourceShopItems not implemented")
}
func (UnimplementedWalletServiceServer) UpsertResourceShopItems(context.Context, *UpsertResourceShopItemsRequest) (*UpsertResourceShopItemsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpsertResourceShopItems not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpsertResourceShopItems not implemented")
}
func (UnimplementedWalletServiceServer) SetResourceShopItemStatus(context.Context, *SetResourceShopItemStatusRequest) (*ResourceShopItemResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetResourceShopItemStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetResourceShopItemStatus not implemented")
}
func (UnimplementedWalletServiceServer) PurchaseResourceShopItem(context.Context, *PurchaseResourceShopItemRequest) (*PurchaseResourceShopItemResponse, error) {
return nil, status.Error(codes.Unimplemented, "method PurchaseResourceShopItem not implemented")
return nil, status.Errorf(codes.Unimplemented, "method PurchaseResourceShopItem not implemented")
}
func (UnimplementedWalletServiceServer) ListRechargeBills(context.Context, *ListRechargeBillsRequest) (*ListRechargeBillsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRechargeBills not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRechargeBills not implemented")
}
func (UnimplementedWalletServiceServer) GetWalletOverview(context.Context, *GetWalletOverviewRequest) (*GetWalletOverviewResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetWalletOverview not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetWalletOverview not implemented")
}
func (UnimplementedWalletServiceServer) GetWalletValueSummary(context.Context, *GetWalletValueSummaryRequest) (*GetWalletValueSummaryResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetWalletValueSummary not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetWalletValueSummary not implemented")
}
func (UnimplementedWalletServiceServer) GetUserGiftWall(context.Context, *GetUserGiftWallRequest) (*GetUserGiftWallResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetUserGiftWall not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetUserGiftWall not implemented")
}
func (UnimplementedWalletServiceServer) ListRechargeProducts(context.Context, *ListRechargeProductsRequest) (*ListRechargeProductsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRechargeProducts not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRechargeProducts not implemented")
}
func (UnimplementedWalletServiceServer) ConfirmGooglePayment(context.Context, *ConfirmGooglePaymentRequest) (*ConfirmGooglePaymentResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ConfirmGooglePayment not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ConfirmGooglePayment not implemented")
}
func (UnimplementedWalletServiceServer) ListAdminRechargeProducts(context.Context, *ListAdminRechargeProductsRequest) (*ListAdminRechargeProductsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListAdminRechargeProducts not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListAdminRechargeProducts not implemented")
}
func (UnimplementedWalletServiceServer) CreateRechargeProduct(context.Context, *CreateRechargeProductRequest) (*RechargeProductResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateRechargeProduct not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateRechargeProduct not implemented")
}
func (UnimplementedWalletServiceServer) UpdateRechargeProduct(context.Context, *UpdateRechargeProductRequest) (*RechargeProductResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateRechargeProduct not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateRechargeProduct not implemented")
}
func (UnimplementedWalletServiceServer) DeleteRechargeProduct(context.Context, *DeleteRechargeProductRequest) (*DeleteRechargeProductResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteRechargeProduct not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DeleteRechargeProduct not implemented")
}
func (UnimplementedWalletServiceServer) ListThirdPartyPaymentChannels(context.Context, *ListThirdPartyPaymentChannelsRequest) (*ListThirdPartyPaymentChannelsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListThirdPartyPaymentChannels not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListThirdPartyPaymentChannels not implemented")
}
func (UnimplementedWalletServiceServer) SetThirdPartyPaymentMethodStatus(context.Context, *SetThirdPartyPaymentMethodStatusRequest) (*ThirdPartyPaymentMethodResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetThirdPartyPaymentMethodStatus not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetThirdPartyPaymentMethodStatus not implemented")
}
func (UnimplementedWalletServiceServer) UpdateThirdPartyPaymentRate(context.Context, *UpdateThirdPartyPaymentRateRequest) (*ThirdPartyPaymentMethodResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateThirdPartyPaymentRate not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateThirdPartyPaymentRate not implemented")
}
func (UnimplementedWalletServiceServer) ListH5RechargeOptions(context.Context, *H5RechargeOptionsRequest) (*H5RechargeOptionsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListH5RechargeOptions not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListH5RechargeOptions not implemented")
}
func (UnimplementedWalletServiceServer) CreateH5RechargeOrder(context.Context, *CreateH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateH5RechargeOrder not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateH5RechargeOrder not implemented")
}
func (UnimplementedWalletServiceServer) SubmitH5RechargeTx(context.Context, *SubmitH5RechargeTxRequest) (*H5RechargeOrderResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SubmitH5RechargeTx not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SubmitH5RechargeTx not implemented")
}
func (UnimplementedWalletServiceServer) GetH5RechargeOrder(context.Context, *GetH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetH5RechargeOrder not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetH5RechargeOrder not implemented")
}
func (UnimplementedWalletServiceServer) HandleMifapayNotify(context.Context, *HandleMifapayNotifyRequest) (*HandleMifapayNotifyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method HandleMifapayNotify not implemented")
return nil, status.Errorf(codes.Unimplemented, "method HandleMifapayNotify not implemented")
}
func (UnimplementedWalletServiceServer) GetDiamondExchangeConfig(context.Context, *GetDiamondExchangeConfigRequest) (*GetDiamondExchangeConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetDiamondExchangeConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetDiamondExchangeConfig not implemented")
}
func (UnimplementedWalletServiceServer) ListWalletTransactions(context.Context, *ListWalletTransactionsRequest) (*ListWalletTransactionsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListWalletTransactions not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListWalletTransactions not implemented")
}
func (UnimplementedWalletServiceServer) ListVipPackages(context.Context, *ListVipPackagesRequest) (*ListVipPackagesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListVipPackages not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListVipPackages not implemented")
}
func (UnimplementedWalletServiceServer) GetMyVip(context.Context, *GetMyVipRequest) (*GetMyVipResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetMyVip not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetMyVip not implemented")
}
func (UnimplementedWalletServiceServer) PurchaseVip(context.Context, *PurchaseVipRequest) (*PurchaseVipResponse, error) {
return nil, status.Error(codes.Unimplemented, "method PurchaseVip not implemented")
return nil, status.Errorf(codes.Unimplemented, "method PurchaseVip not implemented")
}
func (UnimplementedWalletServiceServer) DebitCPBreakupFee(context.Context, *DebitCPBreakupFeeRequest) (*DebitCPBreakupFeeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DebitCPBreakupFee not implemented")
return nil, status.Errorf(codes.Unimplemented, "method DebitCPBreakupFee not implemented")
}
func (UnimplementedWalletServiceServer) DebitWheelDraw(context.Context, *DebitWheelDrawRequest) (*DebitWheelDrawResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DebitWheelDraw not implemented")
}
func (UnimplementedWalletServiceServer) GrantVip(context.Context, *GrantVipRequest) (*GrantVipResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GrantVip not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GrantVip not implemented")
}
func (UnimplementedWalletServiceServer) ListAdminVipLevels(context.Context, *ListAdminVipLevelsRequest) (*ListAdminVipLevelsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListAdminVipLevels not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListAdminVipLevels not implemented")
}
func (UnimplementedWalletServiceServer) UpdateAdminVipLevels(context.Context, *UpdateAdminVipLevelsRequest) (*UpdateAdminVipLevelsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateAdminVipLevels not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateAdminVipLevels not implemented")
}
func (UnimplementedWalletServiceServer) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreditTaskReward not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreditTaskReward not implemented")
}
func (UnimplementedWalletServiceServer) CreditLuckyGiftReward(context.Context, *CreditLuckyGiftRewardRequest) (*CreditLuckyGiftRewardResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreditLuckyGiftReward not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreditLuckyGiftReward not implemented")
}
func (UnimplementedWalletServiceServer) CreditWheelReward(context.Context, *CreditWheelRewardRequest) (*CreditWheelRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditWheelReward not implemented")
}
func (UnimplementedWalletServiceServer) CreditRoomTurnoverReward(context.Context, *CreditRoomTurnoverRewardRequest) (*CreditRoomTurnoverRewardResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreditRoomTurnoverReward not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreditRoomTurnoverReward not implemented")
}
func (UnimplementedWalletServiceServer) CreditInviteActivityReward(context.Context, *CreditInviteActivityRewardRequest) (*CreditInviteActivityRewardResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreditInviteActivityReward not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreditInviteActivityReward not implemented")
}
func (UnimplementedWalletServiceServer) CreditAgencyOpeningReward(context.Context, *CreditAgencyOpeningRewardRequest) (*CreditAgencyOpeningRewardResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreditAgencyOpeningReward not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreditAgencyOpeningReward not implemented")
}
func (UnimplementedWalletServiceServer) ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ApplyGameCoinChange not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ApplyGameCoinChange not implemented")
}
func (UnimplementedWalletServiceServer) GetRedPacketConfig(context.Context, *GetRedPacketConfigRequest) (*GetRedPacketConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRedPacketConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetRedPacketConfig not implemented")
}
func (UnimplementedWalletServiceServer) UpdateRedPacketConfig(context.Context, *UpdateRedPacketConfigRequest) (*UpdateRedPacketConfigResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateRedPacketConfig not implemented")
return nil, status.Errorf(codes.Unimplemented, "method UpdateRedPacketConfig not implemented")
}
func (UnimplementedWalletServiceServer) CreateRedPacket(context.Context, *CreateRedPacketRequest) (*CreateRedPacketResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateRedPacket not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreateRedPacket not implemented")
}
func (UnimplementedWalletServiceServer) ClaimRedPacket(context.Context, *ClaimRedPacketRequest) (*ClaimRedPacketResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ClaimRedPacket not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ClaimRedPacket not implemented")
}
func (UnimplementedWalletServiceServer) ListRedPackets(context.Context, *ListRedPacketsRequest) (*ListRedPacketsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRedPackets not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ListRedPackets not implemented")
}
func (UnimplementedWalletServiceServer) GetRedPacket(context.Context, *GetRedPacketRequest) (*GetRedPacketResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRedPacket not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetRedPacket not implemented")
}
func (UnimplementedWalletServiceServer) ExpireRedPackets(context.Context, *ExpireRedPacketsRequest) (*ExpireRedPacketsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ExpireRedPackets not implemented")
return nil, status.Errorf(codes.Unimplemented, "method ExpireRedPackets not implemented")
}
func (UnimplementedWalletServiceServer) RetryRedPacketRefund(context.Context, *RetryRedPacketRefundRequest) (*RetryRedPacketRefundResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RetryRedPacketRefund not implemented")
return nil, status.Errorf(codes.Unimplemented, "method RetryRedPacketRefund not implemented")
}
func (UnimplementedWalletServiceServer) mustEmbedUnimplementedWalletServiceServer() {}
func (UnimplementedWalletServiceServer) testEmbeddedByValue() {}
@ -1542,7 +1574,7 @@ type UnsafeWalletServiceServer interface {
}
func RegisterWalletServiceServer(s grpc.ServiceRegistrar, srv WalletServiceServer) {
// If the following call panics, it indicates UnimplementedWalletServiceServer was
// If the following call pancis, it indicates UnimplementedWalletServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2704,6 +2736,24 @@ func _WalletService_DebitCPBreakupFee_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler)
}
func _WalletService_DebitWheelDraw_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DebitWheelDrawRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).DebitWheelDraw(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_DebitWheelDraw_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).DebitWheelDraw(ctx, req.(*DebitWheelDrawRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_GrantVip_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GrantVipRequest)
if err := dec(in); err != nil {
@ -2794,6 +2844,24 @@ func _WalletService_CreditLuckyGiftReward_Handler(srv interface{}, ctx context.C
return interceptor(ctx, in, info, handler)
}
func _WalletService_CreditWheelReward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreditWheelRewardRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).CreditWheelReward(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_CreditWheelReward_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).CreditWheelReward(ctx, req.(*CreditWheelRewardRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_CreditRoomTurnoverReward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreditRoomTurnoverRewardRequest)
if err := dec(in); err != nil {
@ -3273,6 +3341,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
MethodName: "DebitCPBreakupFee",
Handler: _WalletService_DebitCPBreakupFee_Handler,
},
{
MethodName: "DebitWheelDraw",
Handler: _WalletService_DebitWheelDraw_Handler,
},
{
MethodName: "GrantVip",
Handler: _WalletService_GrantVip_Handler,
@ -3293,6 +3365,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
MethodName: "CreditLuckyGiftReward",
Handler: _WalletService_CreditLuckyGiftReward_Handler,
},
{
MethodName: "CreditWheelReward",
Handler: _WalletService_CreditWheelReward_Handler,
},
{
MethodName: "CreditRoomTurnoverReward",
Handler: _WalletService_CreditRoomTurnoverReward_Handler,

View File

@ -24,6 +24,7 @@ services:
command: ["sh", "mqbroker", "-n", "rocketmq-namesrv:9876", "-c", "/home/rocketmq/broker.conf"]
ports:
- "19009:10909"
- "10911:10911"
- "19011:10911"
volumes:
- ${DEPLOY_PLATFORM_ROOT:-../deploy-platform}/deploy/rocketmq/broker.conf:/home/rocketmq/broker.conf:ro

View File

@ -0,0 +1,444 @@
# 通用抽奖引擎技术文档
本文档描述当前服务端通用抽奖能力的代码结构、运行口径和转盘落地方式。抽奖通用能力目前由 `activity-service` 持有,钱包入账由 `wallet-service` 持有。幸运礼物已经复用通用权重选择转盘新增独立配置、抽奖记录、RTP 统计、钱包 reason 和后台查询入口。
## 一、边界和结论
| 模块 | Owner | 当前职责 | 不做什么 | 代码位置 |
| --- | --- | --- | --- | --- |
| 通用抽奖引擎 | `activity-service` | 候选归一化、权重随机、奖品类型归一化、RTP 计值归一化 | 不读取数据库、不判断资格、不改奖池、不发奖 | `services/activity-service/internal/domain/lotteryengine/engine.go` |
| 幸运礼物 | `activity-service` | 继续拥有幸运礼物规则、奖池、RTP 窗口、风控、记录和 outbox候选随机改为调用通用引擎 | 不改变原有幸运礼物 RTP 观察和不可支付过滤逻辑 | `services/activity-service/internal/storage/mysql/lucky_gift_repository.go` |
| 转盘 | `activity-service` | 独立规则版本、奖档、奖池、RTP 窗口、抽奖记录、后台统计、金币快路径发奖 | 不复用幸运礼物表;不把道具/装扮价值计入 RTP | `services/activity-service/internal/domain/wheel``internal/service/wheel``internal/storage/mysql/wheel_repository.go` |
| 钱包 reason | `wallet-service` | 新增 `wheel_reward` 入账 reason、交易、分录、钱包 outbox | 不判断转盘中奖资格,不计算 RTP | `services/wallet-service/internal/service/wallet/service.go``internal/storage/mysql/repository.go` |
| Proto/gRPC | `api/proto` + transport | 暴露转盘抽奖和后台配置/记录/统计 RPC | 当前未新增 gateway HTTP 包装 | `api/proto/activity/v1/activity.proto``services/activity-service/internal/transport/grpc/wheel_server.go` |
核心原则:
1. 通用引擎只负责“从已过滤候选里按权重选中一个奖档”。
2. 业务模块必须在调用引擎前完成资格、价格、奖池、风控和预算过滤。
3. RTP 只使用 `rtp_value_coins` 作为分子;转盘道具和装扮无论配置价值多少,运行态都强制为 0。
4. 金币发放必须走钱包独立 reason `wheel_reward`,不能混用 `lucky_gift_reward`
5. 转盘配置、记录和统计使用 `wheel_*` 表,不能写入幸运礼物表。
## 二、代码索引
### 2.1 通用抽奖引擎
| 文件 | 关键符号 | 说明 |
| --- | --- | --- |
| `services/activity-service/internal/domain/lotteryengine/engine.go` | `Candidate` | 引擎唯一输入视图:`ID``Weight``PrizeKind``RTPValueCoins` |
| 同上 | `NormalizeCandidates` | 复制并清洗候选;负权重拒绝;全部权重为 0 时退化为等权随机 |
| 同上 | `SelectWeighted` | 使用 `crypto/rand` 生成随机下标,按权重选择候选 |
| 同上 | `NormalizePrizeKind` | 把 `coin/gift/prop/dress` 及同义值归一化 |
| 同上 | `NormalizeRTPValueCoins` | 金币/礼物保留非负 RTP 价值;`prop/dress` 强制归零 |
| `services/activity-service/internal/domain/lotteryengine/engine_test.go` | 单测 | 覆盖权重选择、全 0 权重兜底、道具/装扮 RTP 归零 |
### 2.2 转盘代码
| 文件 | 关键符号 | 说明 |
| --- | --- | --- |
| `services/activity-service/internal/domain/wheel/wheel.go` | `RuleConfig``Tier``DrawCommand``DrawResult``DrawSummary` | 转盘领域模型 |
| `services/activity-service/internal/service/wheel/config.go` | `NormalizeRuleConfig``ValidateRuleConfig` | 后台配置发布前归一化和硬边界校验 |
| `services/activity-service/internal/service/wheel/service.go` | `Service.Draw``UpsertConfig``GetConfig``ListDraws``GetDrawSummary` | 转盘用例入口 |
| 同上 | `creditCoinRewardFastPath` | 金币奖品同步调用钱包 `CreditWheelReward`,成功后回写 draw、统计和 outbox |
| `services/activity-service/internal/storage/mysql/wheel_repository.go` | `PublishWheelRuleConfig` | 发布不可变规则版本和奖档 |
| 同上 | `ExecuteWheelDraw` | 转盘抽奖主事务幂等、规则锁定、价格校验、RTP 窗口、奖池、记录、统计、outbox |
| 同上 | `executeSingleWheelDraw` | 单次命中奖档、更新 RTP 窗口、写 draw record 和 outbox |
| 同上 | `wheelPayableCandidates` | 按奖池容量和单次 RTP 上限过滤候选;道具/装扮 RTP 价值归零 |
| 同上 | `ListWheelDraws``GetWheelDrawSummary` | 后台抽奖明细和汇总统计读取 |
| 同上 | `MarkWheelDrawsGranted` | 金币发放成功后回写记录、统计和 `activity_outbox` |
| `services/activity-service/internal/storage/mysql/wheel_schema.go` | `ensureWheelTables` | 运行时兜底创建 `wheel_*` 表 |
| `services/activity-service/deploy/mysql/initdb/001_activity_service.sql` | `wheel_*` DDL | 新环境初始化表结构 |
| `services/activity-service/internal/transport/grpc/wheel_server.go` | `WheelServer``AdminWheelServer` | 转盘 App 抽奖和后台配置/统计 gRPC |
| `services/activity-service/internal/app/services.go` | `wheelservice.New` | activity-service 装配转盘 service |
| `services/activity-service/internal/app/grpc_registration.go` | `RegisterWheelServiceServer``RegisterAdminWheelServiceServer` | 注册转盘 gRPC 服务 |
### 2.3 钱包代码
| 文件 | 关键符号 | 说明 |
| --- | --- | --- |
| `api/proto/wallet/v1/wallet.proto` | `CreditWheelRewardRequest``CreditWheelReward` | 钱包转盘金币奖品入账 RPC |
| `services/wallet-service/internal/domain/ledger/ledger.go` | `WheelRewardCommand``WheelRewardReceipt` | 钱包 service/repository 间领域命令 |
| `services/wallet-service/internal/service/wallet/service.go` | `CreditWheelReward` | 校验转盘金币奖品入账命令,固定钱包职责边界 |
| `services/wallet-service/internal/storage/mysql/repository.go` | `bizTypeWheelReward``CreditWheelReward` | 交易、账户余额、分录和钱包 outbox 同事务提交 |
| 同上 | `wheelRewardRequestHash` | 保证同一转盘 draw 幂等入账 |
| `services/wallet-service/internal/transport/grpc/server.go` | `CreditWheelReward` | gRPC 请求转换为钱包领域命令 |
### 2.4 Proto 契约
| 文件 | RPC/Message | 说明 |
| --- | --- | --- |
| `api/proto/activity/v1/activity.proto` | `WheelService.ExecuteWheelDraw` | 扣费后执行转盘抽奖 |
| 同上 | `AdminWheelService.GetWheelConfig` | 后台读取当前转盘配置 |
| 同上 | `AdminWheelService.UpsertWheelConfig` | 后台发布新规则版本 |
| 同上 | `AdminWheelService.ListWheelDraws` | 后台查看抽奖明细 |
| 同上 | `AdminWheelService.GetWheelDrawSummary` | 后台查看转盘统计汇总 |
| 同上 | `WheelRuleConfig``WheelPrizeTier` | 转盘规则和奖档配置 |
| 同上 | `WheelDrawMeta``WheelDrawResult``WheelDrawSummary` | 抽奖入参、结果和统计 |
| `api/proto/wallet/v1/wallet.proto` | `CreditWheelRewardRequest``CreditWheelRewardResponse` | 转盘金币发放钱包契约 |
## 三、通用引擎模型
### 3.1 Candidate
`lotteryengine.Candidate` 是通用引擎唯一关心的数据:
| 字段 | 含义 | 规则 |
| --- | --- | --- |
| `ID` | 业务奖档 ID | 引擎只原样返回,业务侧负责保证唯一 |
| `Weight` | 权重 | 负数拒绝;正数参与随机;全部为 0 时每个候选权重按 1 处理 |
| `PrizeKind` | 奖品类型 | 归一化为 `coin/gift/prop/dress` 等标准值 |
| `RTPValueCoins` | 计入 RTP 的金币价值 | 负数按 0`prop/dress` 强制为 0 |
引擎不直接使用 `RewardCoins``RewardID`、库存、奖池余额、用户信息。这些都属于业务模块。
### 3.2 权重选择
流程:
1. 调用方传入已过滤候选。
2. `NormalizeCandidates` 清洗权重、奖品类型和 RTP 价值。
3. `SelectWeighted` 汇总权重。
4. 用 `crypto/rand.Int` 生成 `[0,totalWeight)` 的随机数。
5. 按权重区间返回命中的 `Candidate`
设计原因:
| 设计 | 原因 |
| --- | --- |
| 使用 `crypto/rand` | 高价值奖品不能被时间种子或进程启动顺序预测 |
| 全 0 权重兜底为等权 | 兼容旧配置或临时配置错误,避免整个活动不可抽 |
| 负权重直接失败 | 负权重没有业务语义,继续运行会让概率不可解释 |
| 不在引擎内做 RTP 修正 | RTP 修正依赖奖池、预算、用户风险和活动策略,必须留在业务层 |
## 四、转盘配置模型
### 4.1 RuleConfig
`domain/wheel.RuleConfig` 是一个不可变版本快照:
| 字段 | 含义 |
| --- | --- |
| `WheelID` | 转盘 ID空值归一为 `default` |
| `RuleVersion` | 不可变规则版本,每次发布 +1 |
| `Enabled` | 是否启用 |
| `DrawPriceCoins` | 单抽价格 |
| `TargetRTPPPM` | RTP 目标ppm |
| `PoolRatePPM` | 每笔消耗进入转盘奖池比例ppm |
| `SettlementWindowDraws` | RTP 观察窗口抽数 |
| `InitialPoolCoins` | 初始奖池水位 |
| `PoolReserveCoins` | 奖池保底水位 |
| `MaxSingleRTPPayout` | 单次可支付的 RTP 价值上限 |
| `Tiers` | 奖档列表 |
### 4.2 Tier
| 字段 | 含义 | 特殊规则 |
| --- | --- | --- |
| `TierID` | 奖档 ID | 同一规则版本内必须唯一 |
| `DisplayName` | 展示名称快照 | 只用于后台和前端展示 |
| `RewardType` | `coin/gift/prop/dress` | 会调用通用引擎归一化 |
| `RewardID` | 礼物、道具、装扮资源 ID | 非金币奖品必填 |
| `RewardCount` | 奖励数量 | 小于等于 0 时归一为 1 |
| `RewardCoins` | 金币奖励金额 | `coin` 使用 |
| `RTPValueCoins` | 计入 RTP 的价值 | `prop/dress` 强制为 0 |
| `WeightPPM` | 随机权重 | 当前作为权重值使用,不要求总和等于 1000000 |
| `Enabled` | 是否启用 | 至少需要一个启用奖档 |
| `MetadataJSON` | 展示和发放扩展快照 | 空值或非法 JSON 归一为 `{}` |
### 4.3 配置校验
入口:`services/activity-service/internal/service/wheel/config.go`
规则:
1. `draw_price_coins` 必须大于 0。
2. `target_rtp_ppm``pool_rate_ppm` 不能为负。
3. `settlement_window_draws` 必须大于 0。
4. 奖档不能为空。
5. `tier_id` 必须唯一。
6. `weight_ppm` 不能为负。
7. `coin` 奖档的 `reward_coins` 不能为负。
8. `gift/prop/dress` 必须有 `reward_id`
9. `prop/dress``rtp_value_coins` 在归一化后必须为 0。
10. 至少一个奖档启用。
## 五、转盘抽奖链路
### 5.1 调用前提
转盘抽奖 RPC 是扣费后的服务端事实入口:
| 阶段 | Owner | 说明 |
| --- | --- | --- |
| 扣费 | 调用方 + wallet-service | 先扣用户金币,拿到真实 `coin_spent``paid_at_ms` |
| 抽奖 | activity-service | 使用 `ExecuteWheelDraw` 固化中奖结果和 RTP |
| 金币发放 | wallet-service | `coin` 奖品通过 `CreditWheelReward` 入账 |
| 礼物/道具/装扮发放 | 对应资产 owner | 当前只固化 pending 记录和 outbox 事实,后续接资产发放接口 |
转盘 repository 会校验:
```text
coin_spent == draw_price_coins * draw_count
```
这样避免客户端或调用方伪造 RTP 分母。
### 5.2 主事务
入口:`Repository.ExecuteWheelDraw`
事务内顺序:
1. 归一化 `wheel_id``draw_count`
2. 按 `command_id` 收集已有 draw完整存在则幂等返回。
3. 如果只存在部分子抽,返回冲突,避免重复发奖。
4. 锁定当前启用的转盘规则版本。
5. 校验 `coin_spent` 与当前单价一致。
6. 获取或创建 RTP 窗口。
7. 获取或创建转盘奖池。
8. 按 `draw_count` 拆分每次消耗。
9. 每一抽调用 `executeSingleWheelDraw`
10. 汇总写回奖池净变化。
11. 更新后台统计。
12. 提交事务。
### 5.3 单抽
入口:`executeSingleWheelDraw`
单抽顺序:
1. `wheelPayableCandidates` 过滤可支付候选。
2. 组装 `lotteryengine.Candidate`
3. 调用 `lotteryengine.SelectWeighted` 命中奖档。
4. 用命中奖档的 `RTPValueCoins` 更新奖池和 RTP 窗口。
5. 写入 `wheel_draw_records`
6. 如果奖品需要发放,写入 `activity_outbox`,事件类型为 `WheelRewardSettlement`
7. 返回 `DrawResult`
候选过滤口径:
| 过滤项 | 说明 |
| --- | --- |
| `tier.Enabled` | 未启用奖档不参与 |
| `RTPValueCoins` | `prop/dress` 强制为 0 |
| 奖池容量 | `pool.balance - reserve_floor` |
| 单次上限 | `max_single_rtp_payout` 大于 0 时限制容量 |
注意:道具/装扮 `RTPValueCoins=0`,所以不会因为展示价值占用奖池,也不会提高 RTP 分子。
### 5.4 批量抽奖
当前转盘支持 `draw_count`
| 功能 | 代码 | 说明 |
| --- | --- | --- |
| 子命令 ID | `wheelSubCommandID` | 单抽使用原 command多抽追加 `#000001` 这类后缀 |
| 金额拆分 | 复用 `luckyDrawUnitSpend` | 总金额按次数拆分,余数给前几抽 |
| 幂等聚合 | `collectWheelDrawsByCommand``aggregateWheelDrawResults` | 完整存在则聚合返回 |
| 发放限制 | `creditCoinRewardFastPath` | 当前只对单抽金币做同步快路径,多抽保留 pending/outbox |
## 六、RTP 口径
### 6.1 分子和分母
| 项 | 字段 | 来源 |
| --- | --- | --- |
| RTP 分母 | `coin_spent` / `wager_coins` | 用户实际扣费金币 |
| RTP 分子 | `rtp_value_coins` / `actual_rtp_value_coins` | 命中奖档计入 RTP 的价值 |
| 目标值 | `target_rtp_ppm` | 后台规则配置 |
| 当前值 | `actual_rtp_ppm` | `actual_rtp_value_coins * 1000000 / wager_coins` |
### 6.2 奖品类型口径
| 奖品类型 | 是否可配置 | 是否发放 | 是否计入 RTP |
| --- | --- | --- | --- |
| `coin` | 是 | 钱包 `wheel_reward` | 是,使用 `rtp_value_coins` |
| `gift` | 是 | 待接礼物/资源 owner | 是,使用 `rtp_value_coins` |
| `prop` | 是 | 待接道具 owner | 否,强制 0 |
| `dress` | 是 | 待接装扮 owner | 否,强制 0 |
强制归零的位置:
| 层 | 代码 |
| --- | --- |
| 通用引擎 | `lotteryengine.NormalizeRTPValueCoins` |
| 转盘配置 | `wheel.NormalizeRuleConfig` |
| 转盘仓储 | `normalizeWheelTiers``wheelPayableCandidates` |
| 转盘测试 | `services/activity-service/internal/service/wheel/config_test.go``domain/lotteryengine/engine_test.go` |
## 七、发奖和 outbox
### 7.1 金币奖品
金币奖品路径:
1. `ExecuteWheelDraw` 写入 `wheel_draw_records`,初始状态为 `pending`
2. `Service.Draw` 在事务提交后进入 `creditCoinRewardFastPath`
3. 调用 wallet-service `CreditWheelReward`
4. wallet-service 以 `biz_type=wheel_reward` 写交易、账户、分录和 wallet outbox。
5. activity-service 调用 `MarkWheelDrawsGranted`
6. `MarkWheelDrawsGranted` 同事务更新:
- `wheel_draw_records.reward_status = granted`
- `wheel_draw_records.reward_transaction_id`
- `wheel_draw_stats.pending_draws/granted_draws`
- `activity_outbox.status = delivered`
钱包 reason
| 项 | 值 |
| --- | --- |
| wallet biz type | `wheel_reward` |
| RPC reason | `wheel_reward` |
| command ID | `wheel_reward:<draw_id>` |
| 钱包事件 | `WalletWheelRewardCredited` |
### 7.2 礼物/道具/装扮奖品
当前状态:
1. 抽奖结果会落 `wheel_draw_records`
2. `reward_status``pending`
3. `activity_outbox` 会写 `WheelRewardSettlement`
4. 具体发放 worker 和资产 owner RPC 还没有接入。
后续接入时必须遵守:
1. 不允许在 activity-service 直接改钱包或资产表。
2. 发放成功后调用转盘仓储状态回写方法,保持 draw record、统计和 outbox 一致。
3. 道具/装扮发放成功也不能把展示价值写入 RTP。
4. 礼物如果要计入 RTP必须使用抽奖时固化的 `rtp_value_coins`,不能按发放时礼物价格重新估值。
## 八、后台记录和统计
### 8.1 明细
入口:`AdminWheelService.ListWheelDraws`
仓储:`Repository.ListWheelDraws`
支持筛选:
| 字段 | 说明 |
| --- | --- |
| `wheel_id` | 转盘 ID |
| `user_id` | 用户 ID |
| `status` | `pending/granted/failed` |
| `page/page_size` | 分页,`page_size` 最大 100 |
返回字段包括:
`draw_id``command_id``wheel_id``rule_version``selected_tier_id``reward_type``reward_id``reward_count``reward_coins``rtp_value_coins``reward_status``wallet_transaction_id``rtp_window_index``metadata_json`
### 8.2 汇总
入口:`AdminWheelService.GetWheelDrawSummary`
仓储:`Repository.GetWheelDrawSummary`
统计字段:
| 字段 | 说明 |
| --- | --- |
| `total_draws` | 抽奖次数 |
| `unique_users` | 去重用户数 |
| `total_spent_coins` | 总消耗金币 |
| `total_rtp_value_coins` | 计入 RTP 的返奖价值 |
| `actual_rtp_ppm` | 实际 RTP |
| `pending_draws` | 待发放 |
| `granted_draws` | 已发放 |
| `failed_draws` | 失败 |
当只按 `wheel_id` 汇总时,优先读 `wheel_draw_stats`;带 `user_id/status` 过滤时直接聚合 `wheel_draw_records`
## 九、数据表
| 表 | 用途 | 代码 |
| --- | --- | --- |
| `wheel_rule_versions` | 转盘不可变规则版本 | `wheel_schema.go``001_activity_service.sql` |
| `wheel_prize_tiers` | 每个规则版本的奖档快照 | 同上 |
| `wheel_rtp_windows` | RTP 观察窗口 | `getOpenWheelRTPWindow``createWheelRTPWindow` |
| `wheel_pools` | 转盘奖池水位 | `getOrCreateWheelPool``persistWheelPoolDelta` |
| `wheel_draw_records` | 抽奖事实明细 | `executeSingleWheelDraw``ListWheelDraws` |
| `wheel_draw_stats` | 后台汇总统计 | `updateWheelStats``GetWheelDrawSummary` |
| `wheel_draw_stat_users` | 统计去重用户 | `updateWheelStats` |
| `activity_outbox` | 转盘发奖补偿事实 | `insertWheelRewardOutbox``MarkWheelDrawsGranted` |
| `wallet_transactions` | 钱包交易 | `wallet-service/internal/storage/mysql.Repository.CreditWheelReward` |
| `wallet_outbox` | 钱包余额和奖励事实 | 同上 |
## 十、和幸运礼物的关系
幸运礼物不是直接迁移成转盘,也不是把转盘塞进幸运礼物:
| 项 | 幸运礼物 | 转盘 |
| --- | --- | --- |
| 规则维度 | `pool_id`,房间送礼场景 | `wheel_id`H5/活动转盘场景 |
| 奖品 | 金币返奖 | 金币、礼物、道具、装扮 |
| RTP 口径 | 原幸运礼物基础返奖口径 | `rtp_value_coins`,道具/装扮强制 0 |
| 记录表 | `lucky_draw_records` | `wheel_draw_records` |
| 钱包 reason | `lucky_gift_reward` | `wheel_reward` |
| 通用引擎使用 | 候选随机复用 `lotteryengine.SelectWeighted` | 候选随机和 RTP 归一化都复用 |
幸运礼物保留自己的奖池、风控、用户阶段和 room IM 逻辑。通用引擎只替换了“已过滤候选里的权重随机”这一小段,避免改动原业务行为。
## 十一、扩展新抽奖活动的步骤
新增一个抽奖活动时,不要复制幸运礼物或转盘整套逻辑。按下面顺序接入:
1. 新建独立 domain定义自己的 `RuleConfig``Tier``DrawCommand``DrawResult`
2. 新建 service负责入参归一化、配置校验和跨服务调用。
3. 新建 storage负责规则版本、奖池、RTP 窗口、记录、统计和 outbox。
4. 业务层先过滤资格、预算、库存、奖池和风控。
5. 把过滤后的候选转换成 `lotteryengine.Candidate`
6. 调用 `lotteryengine.SelectWeighted`
7. 用返回候选固化抽奖结果。
8. 如果有金币奖品,在 wallet-service 增加独立 reason不复用已有 reason。
9. 如果有非金币奖品,接对应 owner service 的发放接口。
10. 补 proto、gRPC、表结构、initdb 和测试。
必须明确回答的问题:
| 问题 | 必须落地的位置 |
| --- | --- |
| 奖品有哪些类型 | domain `Tier.RewardType` 和 proto |
| 哪些奖品计入 RTP | `RTPValueCoins` 归一化 |
| 哪个服务发奖 | service 跨服务依赖 |
| 钱包 reason 是什么 | wallet-service domain/service/storage/proto |
| 如何幂等 | command ID、draw ID、钱包 request hash |
| 后台查什么 | draw records 和 summary |
| 失败如何补偿 | activity outbox 或 owner outbox |
## 十二、验证命令
当前相关验证命令:
```bash
PATH=/Users/hy/go/bin:$PATH make proto
go test ./services/activity-service/internal/domain/lotteryengine
go test ./services/activity-service/internal/service/wheel
go test ./services/activity-service/internal/storage/mysql
go test ./services/activity-service/internal/transport/grpc
go test ./services/activity-service/internal/app
go test ./services/wallet-service/internal/service/wallet ./services/wallet-service/internal/storage/mysql ./services/wallet-service/internal/transport/grpc
go test ./services/activity-service/...
```
已知当前仓库全量 `make test` 仍会失败在既有 MifaPay 配置测试:
```text
services/wallet-service/internal/config.TestLoadMifaPayMerchantConfig
mifapay merchant identity mismatch in ../../configs/config.docker.yaml
```
该失败与通用抽奖和转盘改动无关。
## 十三、当前未完成事项
| 事项 | 原因 | 后续接入位置 |
| --- | --- | --- |
| 礼物奖品真实发放 | 需要确认礼物/资源 owner 的发放接口和背包表现 | activity-service 转盘 outbox worker + 对应 owner RPC |
| 道具/装扮真实发放 | 需要确认装扮资源 owner、有效期和背包协议 | activity-service 转盘 outbox worker + resource/wallet owner |
| Gateway HTTP/H5 接口 | 当前只到 activity gRPCH5 页面还需要 gateway 包装 | `services/gateway-service/internal/transport/http/activityapi` |
| 后台管理页面 | 当前只有 gRPC 后台能力,未接 admin web 模块 | `server/admin` |

View File

@ -111,6 +111,29 @@ type RoomEvent struct {
Attributes map[string]string `json:"attributes,omitempty"`
}
func (event RoomEvent) MarshalJSON() ([]byte, error) {
type roomEventAlias RoomEvent
payload, err := json.Marshal(roomEventAlias(event))
if err != nil {
return nil, err
}
var merged map[string]any
if err := json.Unmarshal(payload, &merged); err != nil {
return nil, err
}
for key, value := range event.Attributes {
if strings.TrimSpace(key) == "" {
continue
}
if _, exists := merged[key]; exists {
// 顶层协议字段由 RoomEvent 结构体负责attributes 只能补充展示字段,不能覆盖事件身份。
continue
}
merged[key] = value
}
return json.Marshal(merged)
}
// RoomEntryVehicleSnapshot 是房间系统消息里可直接渲染的座驾素材快照。
// 它故意不包含 wallet 内部状态,只保留客户端展示和过期兜底需要的字段。
type RoomEntryVehicleSnapshot struct {

View File

@ -48,6 +48,24 @@ func TestRealTencentIMCreateSendAndDestroyGroup(t *testing.T) {
if err := realImportAccount(ctx, client, memberUserID); err != nil {
t.Fatalf("real Tencent IM account import failed: %v", err)
}
c2cEventID := fmt.Sprintf("codex_real_im_c2c_%d", nowMS)
c2cPayload, err := json.Marshal(map[string]any{
"event_id": c2cEventID,
"event_type": "codex_real_im_c2c_probe",
"sent_at_ms": nowMS,
})
if err != nil {
t.Fatalf("marshal c2c smoke payload failed: %v", err)
}
if err := client.PublishUserCustomMessage(ctx, CustomUserMessage{
ToAccount: strconv.FormatInt(memberUserID, 10),
EventID: c2cEventID,
Desc: "codex_real_im_c2c_probe",
Ext: "im_c2c_smoke",
PayloadJSON: c2cPayload,
}); err != nil {
t.Fatalf("real Tencent IM send C2C custom message failed: %v", err)
}
if err := realAddGroupMember(ctx, client, groupID, memberUserID); err != nil {
t.Fatalf("real Tencent IM add group member failed: %v", err)
}
@ -82,7 +100,7 @@ func TestRealTencentIMCreateSendAndDestroyGroup(t *testing.T) {
t.Fatalf("real Tencent IM delete group member failed: %v", err)
}
t.Logf("real Tencent IM smoke passed: sdk_app_id=%d group_id=%s event_id=%s removed_user_id=%d", cfg.SDKAppID, groupID, eventID, memberUserID)
t.Logf("real Tencent IM smoke passed: sdk_app_id=%d group_id=%s group_event_id=%s c2c_event_id=%s removed_user_id=%d", cfg.SDKAppID, groupID, eventID, c2cEventID, memberUserID)
}
func realImportAccount(ctx context.Context, client *RESTClient, userID int64) error {

View File

@ -97,6 +97,60 @@ func TestRESTClientPublishRoomEventBuildsCustomMessage(t *testing.T) {
}
}
func TestRESTClientPublishRoomEventFlattensAttributesForClientPayload(t *testing.T) {
var capturedBody map[string]any
client := newTestRESTClient(t, func(request *http.Request) (*http.Response, error) {
if err := json.NewDecoder(request.Body).Decode(&capturedBody); err != nil {
t.Fatalf("decode request body failed: %v", err)
}
return okRESTResponse(), nil
})
err := client.PublishRoomEvent(context.Background(), RoomEvent{
GroupID: "room_1001",
EventID: "evt-robot-lucky-1",
RoomID: "room-1001",
EventType: "lucky_gift_drawn",
ActorUserID: 10001,
TargetUserID: 10002,
GiftValue: 500,
Attributes: map[string]string{
"event_type": "should_not_override",
"draw_id": "robot_lucky_draw_1",
"gift_id": "gift_lucky_1",
"gift_count": "1",
"pool_id": "robot_lucky_display",
"multiplier_ppm": "5000000",
"effective_reward_coins": "500",
"reward_status": "granted",
},
})
if err != nil {
t.Fatalf("PublishRoomEvent failed: %v", err)
}
body, ok := capturedBody["MsgBody"].([]any)
if !ok || len(body) != 1 {
t.Fatalf("unexpected msg body: %+v", capturedBody["MsgBody"])
}
element := body[0].(map[string]any)
content := element["MsgContent"].(map[string]any)
var payload map[string]any
if err := json.Unmarshal([]byte(content["Data"].(string)), &payload); err != nil {
t.Fatalf("decode custom data failed: %v", err)
}
if payload["event_type"] != "lucky_gift_drawn" {
t.Fatalf("attributes must not override top-level event_type: %+v", payload)
}
if payload["draw_id"] != "robot_lucky_draw_1" || payload["effective_reward_coins"] != "500" || payload["reward_status"] != "granted" {
t.Fatalf("lucky gift attributes must be flattened for existing clients: %+v", payload)
}
attributes, ok := payload["attributes"].(map[string]any)
if !ok || attributes["effective_reward_coins"] != "500" {
t.Fatalf("attributes must still be kept for diagnostics and fallback clients: %+v", payload["attributes"])
}
}
func TestRESTClientPublishGroupCustomMessageBuildsBroadcastPayload(t *testing.T) {
var capturedBody map[string]any
client := newTestRESTClient(t, func(request *http.Request) (*http.Response, error) {

View File

@ -70,6 +70,7 @@ import (
userleaderboardmodule "hyapp-admin-server/internal/modules/userleaderboard"
vipconfigmodule "hyapp-admin-server/internal/modules/vipconfig"
weeklystarmodule "hyapp-admin-server/internal/modules/weeklystar"
wheelmodule "hyapp-admin-server/internal/modules/wheel"
"hyapp-admin-server/internal/platform/logging"
"hyapp-admin-server/internal/platform/tencentcos"
"hyapp-admin-server/internal/repository"
@ -272,6 +273,7 @@ func main() {
userclient.NewGRPC(userConn),
auditHandler,
gamemanagementmodule.WithRobotProfileSource(robotProfileSource),
gamemanagementmodule.WithRobotAppearanceServices(walletclient.NewGRPC(walletConn), activityclient.NewGRPC(activityConn)),
),
GiftDiamond: giftdiamondmodule.New(walletDB, auditHandler),
Health: healthmodule.New(store, redisClient, jobStatus),
@ -291,7 +293,7 @@ func main() {
RegistrationReward: registrationrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
RegionBlock: regionblockmodule.New(userDB, auditHandler),
Resource: resourcemodule.New(walletclient.NewGRPC(walletConn), store, userDB, cfg.WalletService.RequestTimeout, auditHandler),
RoomAdmin: roomadminmodule.New(userDB, roomClient, robotClient, auditHandler),
RoomAdmin: roomadminmodule.New(userDB, store, roomClient, robotClient, auditHandler),
RoomRocket: roomrocketmodule.New(roomClient, auditHandler),
RoomTurnoverReward: roomturnoverrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
Search: searchmodule.New(store),
@ -302,6 +304,7 @@ func main() {
UserLeaderboard: userleaderboardmodule.New(walletDB, userDB, roomClient),
VIPConfig: vipconfigmodule.New(walletclient.NewGRPC(walletConn), auditHandler),
WeeklyStar: weeklystarmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
Wheel: wheelmodule.New(activityclient.NewGRPC(activityConn), cfg.ActivityService.RequestTimeout, auditHandler),
}
engine := router.New(cfg, auth, handlers)

View File

@ -48,6 +48,7 @@ type Client interface {
UpdateAgencyOpeningCycle(ctx context.Context, req *activityv1.UpsertAgencyOpeningCycleRequest) (*activityv1.UpsertAgencyOpeningCycleResponse, error)
SetAgencyOpeningCycleStatus(ctx context.Context, req *activityv1.SetAgencyOpeningCycleStatusRequest) (*activityv1.SetAgencyOpeningCycleStatusResponse, error)
ListAgencyOpeningApplications(ctx context.Context, req *activityv1.ListAgencyOpeningApplicationsRequest) (*activityv1.ListAgencyOpeningApplicationsResponse, error)
ReviewAgencyOpeningApplication(ctx context.Context, req *activityv1.ReviewAgencyOpeningApplicationRequest) (*activityv1.ReviewAgencyOpeningApplicationResponse, error)
GetCPWeeklyRankConfig(ctx context.Context, req *activityv1.GetCPWeeklyRankConfigRequest) (*activityv1.GetCPWeeklyRankConfigResponse, error)
UpdateCPWeeklyRankConfig(ctx context.Context, req *activityv1.UpdateCPWeeklyRankConfigRequest) (*activityv1.UpdateCPWeeklyRankConfigResponse, error)
ListCPWeeklyRankSettlements(ctx context.Context, req *activityv1.ListCPWeeklyRankSettlementsRequest) (*activityv1.ListCPWeeklyRankSettlementsResponse, error)
@ -56,11 +57,16 @@ type Client interface {
ListLuckyGiftConfigs(ctx context.Context, req *activityv1.ListLuckyGiftConfigsRequest) (*activityv1.ListLuckyGiftConfigsResponse, error)
ListLuckyGiftDraws(ctx context.Context, req *activityv1.ListLuckyGiftDrawsRequest) (*activityv1.ListLuckyGiftDrawsResponse, error)
GetLuckyGiftDrawSummary(ctx context.Context, req *activityv1.GetLuckyGiftDrawSummaryRequest) (*activityv1.GetLuckyGiftDrawSummaryResponse, error)
GetWheelConfig(ctx context.Context, req *activityv1.GetWheelConfigRequest) (*activityv1.GetWheelConfigResponse, error)
UpsertWheelConfig(ctx context.Context, req *activityv1.UpsertWheelConfigRequest) (*activityv1.UpsertWheelConfigResponse, error)
ListWheelDraws(ctx context.Context, req *activityv1.ListWheelDrawsRequest) (*activityv1.ListWheelDrawsResponse, error)
GetWheelDrawSummary(ctx context.Context, req *activityv1.GetWheelDrawSummaryRequest) (*activityv1.GetWheelDrawSummaryResponse, error)
RemoveRegionBroadcastMember(ctx context.Context, req *activityv1.RemoveRegionBroadcastMemberRequest) (*activityv1.RemoveRegionBroadcastMemberResponse, error)
ListLevelConfig(ctx context.Context, req *activityv1.ListLevelConfigRequest) (*activityv1.ListLevelConfigResponse, error)
UpsertLevelTrack(ctx context.Context, req *activityv1.UpsertLevelTrackRequest) (*activityv1.UpsertLevelTrackResponse, error)
UpsertLevelRule(ctx context.Context, req *activityv1.UpsertLevelRuleRequest) (*activityv1.UpsertLevelRuleResponse, error)
UpsertLevelTier(ctx context.Context, req *activityv1.UpsertLevelTierRequest) (*activityv1.UpsertLevelTierResponse, error)
SetUserLevel(ctx context.Context, req *activityv1.SetUserLevelRequest) (*activityv1.SetUserLevelResponse, error)
CreateInboxMessage(ctx context.Context, req *activityv1.CreateInboxMessageRequest) (*activityv1.CreateInboxMessageResponse, error)
CreateFanoutJob(ctx context.Context, req *activityv1.CreateFanoutJobRequest) (*activityv1.CreateFanoutJobResponse, error)
}
@ -78,7 +84,9 @@ type GRPCClient struct {
agencyOpeningClient activityv1.AdminAgencyOpeningServiceClient
cpWeeklyRankClient activityv1.AdminCPWeeklyRankServiceClient
luckyGiftClient activityv1.AdminLuckyGiftServiceClient
wheelClient activityv1.AdminWheelServiceClient
broadcastClient activityv1.BroadcastServiceClient
levelClient activityv1.GrowthLevelServiceClient
growthClient activityv1.AdminGrowthLevelServiceClient
messageClient activityv1.MessageInboxServiceClient
}
@ -97,7 +105,9 @@ func NewGRPC(conn grpc.ClientConnInterface) *GRPCClient {
agencyOpeningClient: activityv1.NewAdminAgencyOpeningServiceClient(conn),
cpWeeklyRankClient: activityv1.NewAdminCPWeeklyRankServiceClient(conn),
luckyGiftClient: activityv1.NewAdminLuckyGiftServiceClient(conn),
wheelClient: activityv1.NewAdminWheelServiceClient(conn),
broadcastClient: activityv1.NewBroadcastServiceClient(conn),
levelClient: activityv1.NewGrowthLevelServiceClient(conn),
growthClient: activityv1.NewAdminGrowthLevelServiceClient(conn),
messageClient: activityv1.NewMessageInboxServiceClient(conn),
}
@ -255,6 +265,10 @@ func (c *GRPCClient) ListAgencyOpeningApplications(ctx context.Context, req *act
return c.agencyOpeningClient.ListAgencyOpeningApplications(ctx, req)
}
func (c *GRPCClient) ReviewAgencyOpeningApplication(ctx context.Context, req *activityv1.ReviewAgencyOpeningApplicationRequest) (*activityv1.ReviewAgencyOpeningApplicationResponse, error) {
return c.agencyOpeningClient.ReviewAgencyOpeningApplication(ctx, req)
}
func (c *GRPCClient) GetCPWeeklyRankConfig(ctx context.Context, req *activityv1.GetCPWeeklyRankConfigRequest) (*activityv1.GetCPWeeklyRankConfigResponse, error) {
return c.cpWeeklyRankClient.GetCPWeeklyRankConfig(ctx, req)
}
@ -287,6 +301,22 @@ func (c *GRPCClient) GetLuckyGiftDrawSummary(ctx context.Context, req *activityv
return c.luckyGiftClient.GetLuckyGiftDrawSummary(ctx, req)
}
func (c *GRPCClient) GetWheelConfig(ctx context.Context, req *activityv1.GetWheelConfigRequest) (*activityv1.GetWheelConfigResponse, error) {
return c.wheelClient.GetWheelConfig(ctx, req)
}
func (c *GRPCClient) UpsertWheelConfig(ctx context.Context, req *activityv1.UpsertWheelConfigRequest) (*activityv1.UpsertWheelConfigResponse, error) {
return c.wheelClient.UpsertWheelConfig(ctx, req)
}
func (c *GRPCClient) ListWheelDraws(ctx context.Context, req *activityv1.ListWheelDrawsRequest) (*activityv1.ListWheelDrawsResponse, error) {
return c.wheelClient.ListWheelDraws(ctx, req)
}
func (c *GRPCClient) GetWheelDrawSummary(ctx context.Context, req *activityv1.GetWheelDrawSummaryRequest) (*activityv1.GetWheelDrawSummaryResponse, error) {
return c.wheelClient.GetWheelDrawSummary(ctx, req)
}
func (c *GRPCClient) RemoveRegionBroadcastMember(ctx context.Context, req *activityv1.RemoveRegionBroadcastMemberRequest) (*activityv1.RemoveRegionBroadcastMemberResponse, error) {
return c.broadcastClient.RemoveRegionBroadcastMember(ctx, req)
}
@ -307,6 +337,10 @@ func (c *GRPCClient) UpsertLevelTier(ctx context.Context, req *activityv1.Upsert
return c.growthClient.UpsertLevelTier(ctx, req)
}
func (c *GRPCClient) SetUserLevel(ctx context.Context, req *activityv1.SetUserLevelRequest) (*activityv1.SetUserLevelResponse, error) {
return c.levelClient.SetUserLevel(ctx, req)
}
func (c *GRPCClient) CreateInboxMessage(ctx context.Context, req *activityv1.CreateInboxMessageRequest) (*activityv1.CreateInboxMessageResponse, error) {
return c.messageClient.CreateInboxMessage(ctx, req)
}

View File

@ -21,6 +21,8 @@ type Client interface {
UpdateRoomRocketConfig(ctx context.Context, req UpdateRoomRocketConfigRequest) (RoomRocketConfig, error)
GetRoomSeatConfig(ctx context.Context) (RoomSeatConfig, error)
UpdateRoomSeatConfig(ctx context.Context, req UpdateRoomSeatConfigRequest) (RoomSeatConfig, error)
GetHumanRoomRobotConfig(ctx context.Context) (HumanRoomRobotConfig, error)
UpdateHumanRoomRobotConfig(ctx context.Context, req UpdateHumanRoomRobotConfigRequest) (HumanRoomRobotConfig, error)
ListRoomPins(ctx context.Context, req ListRoomPinsRequest) (ListRoomPinsResult, error)
CreateRoomPin(ctx context.Context, req CreateRoomPinRequest) (RoomPin, error)
CancelRoomPin(ctx context.Context, req CancelRoomPinRequest) (RoomPin, error)
@ -162,6 +164,38 @@ type UpdateRoomSeatConfigRequest struct {
DefaultSeatCount int32
}
type HumanRoomRobotConfig struct {
AppCode string
Enabled bool
CandidateRoomMaxOnline int32
RoomFullStopOnline int32
RoomTargetMinOnline int32
RoomTargetMaxOnline int32
RobotStayMinMS int64
RobotStayMaxMS int64
RobotReplaceMinMS int64
RobotReplaceMaxMS int64
NormalGiftIntervalMS int64
NormalGiftIntervalMinMS int64
NormalGiftIntervalMaxMS int64
GiftIDs []string
LuckyGiftIDs []string
SuperLuckyGiftIDs []string
LuckyComboMin int64
LuckyComboMax int64
LuckyPauseMinMS int64
LuckyPauseMaxMS int64
MaxGiftSenders int64
UpdatedByAdminID uint64
CreatedAtMS int64
UpdatedAtMS int64
}
type UpdateHumanRoomRobotConfigRequest struct {
Config HumanRoomRobotConfig
AdminID uint64
}
type RoomPinRoom struct {
RoomID string
RoomShortID string
@ -226,6 +260,11 @@ type RobotRoomGiftRule struct {
LuckyComboMax int64
LuckyPauseMinMS int64
LuckyPauseMaxMS int64
RobotStayMinMS int64
RobotStayMaxMS int64
RobotReplaceMinMS int64
RobotReplaceMaxMS int64
MaxGiftSenders int64
}
type RobotRoom struct {
@ -238,6 +277,8 @@ type RobotRoom struct {
Status string
OwnerRobotUserID int64
RobotUserIDs []int64
ActiveRobotCount int32
SeatCount int32
GiftRule RobotRoomGiftRule
CreatedByAdminID uint64
CreatedAtMS int64
@ -263,6 +304,8 @@ type CreateRobotRoomRequest struct {
RoomName string
RoomAvatar string
VisibleRegionID int64
OwnerCountryCode string
SeatCount int32
GiftRule RobotRoomGiftRule
AdminID uint64
}
@ -415,6 +458,26 @@ func (c *GRPCClient) UpdateRoomSeatConfig(ctx context.Context, req UpdateRoomSea
return roomSeatConfigFromProto(resp.GetConfig()), nil
}
func (c *GRPCClient) GetHumanRoomRobotConfig(ctx context.Context) (HumanRoomRobotConfig, error) {
resp, err := c.queryClient.AdminGetHumanRoomRobotConfig(ctx, &roomv1.AdminGetHumanRoomRobotConfigRequest{Meta: requestMeta(ctx, "", 0, "admin-get-human-room-robot-config")})
if err != nil {
return HumanRoomRobotConfig{}, err
}
return humanRoomRobotConfigFromProto(resp.GetConfig()), nil
}
func (c *GRPCClient) UpdateHumanRoomRobotConfig(ctx context.Context, req UpdateHumanRoomRobotConfigRequest) (HumanRoomRobotConfig, error) {
resp, err := c.client.AdminUpdateHumanRoomRobotConfig(ctx, &roomv1.AdminUpdateHumanRoomRobotConfigRequest{
Meta: requestMeta(ctx, "", int64(req.AdminID), "admin-update-human-room-robot-config"),
Config: humanRoomRobotConfigToProto(req.Config),
AdminId: req.AdminID,
})
if err != nil {
return HumanRoomRobotConfig{}, err
}
return humanRoomRobotConfigFromProto(resp.GetConfig()), nil
}
func (c *GRPCClient) ListRoomPins(ctx context.Context, req ListRoomPinsRequest) (ListRoomPinsResult, error) {
resp, err := c.queryClient.AdminListRoomPins(ctx, &roomv1.AdminListRoomPinsRequest{
Meta: requestMeta(ctx, "", 0, "admin-list-room-pins"),
@ -491,6 +554,8 @@ func (c *GRPCClient) CreateRobotRoom(ctx context.Context, req CreateRobotRoomReq
RoomName: strings.TrimSpace(req.RoomName),
RoomAvatar: strings.TrimSpace(req.RoomAvatar),
VisibleRegionId: req.VisibleRegionID,
OwnerCountryCode: strings.ToUpper(strings.TrimSpace(req.OwnerCountryCode)),
SeatCount: req.SeatCount,
GiftRule: robotRoomGiftRuleToProto(req.GiftRule),
AdminId: req.AdminID,
})
@ -655,6 +720,69 @@ func roomSeatConfigFromProto(input *roomv1.AdminRoomSeatConfig) RoomSeatConfig {
}
}
func humanRoomRobotConfigFromProto(input *roomv1.AdminHumanRoomRobotConfig) HumanRoomRobotConfig {
if input == nil {
return HumanRoomRobotConfig{}
}
config := HumanRoomRobotConfig{
AppCode: input.GetAppCode(),
Enabled: input.GetEnabled(),
CandidateRoomMaxOnline: input.GetCandidateRoomMaxOnline(),
RoomFullStopOnline: input.GetRoomFullStopOnline(),
RoomTargetMinOnline: input.GetRoomTargetMinOnline(),
RoomTargetMaxOnline: input.GetRoomTargetMaxOnline(),
RobotStayMinMS: input.GetRobotStayMinMs(),
RobotStayMaxMS: input.GetRobotStayMaxMs(),
RobotReplaceMinMS: input.GetRobotReplaceMinMs(),
RobotReplaceMaxMS: input.GetRobotReplaceMaxMs(),
NormalGiftIntervalMS: input.GetNormalGiftIntervalMs(),
NormalGiftIntervalMinMS: input.GetNormalGiftIntervalMinMs(),
NormalGiftIntervalMaxMS: input.GetNormalGiftIntervalMaxMs(),
GiftIDs: append([]string(nil), input.GetGiftIds()...),
LuckyGiftIDs: append([]string(nil), input.GetLuckyGiftIds()...),
SuperLuckyGiftIDs: append([]string(nil), input.GetSuperLuckyGiftIds()...),
LuckyComboMin: input.GetLuckyComboMin(),
LuckyComboMax: input.GetLuckyComboMax(),
LuckyPauseMinMS: input.GetLuckyPauseMinMs(),
LuckyPauseMaxMS: input.GetLuckyPauseMaxMs(),
MaxGiftSenders: input.GetMaxGiftSenders(),
UpdatedByAdminID: input.GetUpdatedByAdminId(),
CreatedAtMS: input.GetCreatedAtMs(),
UpdatedAtMS: input.GetUpdatedAtMs(),
}
return config
}
func humanRoomRobotConfigToProto(input HumanRoomRobotConfig) *roomv1.AdminHumanRoomRobotConfig {
out := &roomv1.AdminHumanRoomRobotConfig{
AppCode: input.AppCode,
Enabled: input.Enabled,
CandidateRoomMaxOnline: input.CandidateRoomMaxOnline,
RoomFullStopOnline: input.RoomFullStopOnline,
RoomTargetMinOnline: input.RoomTargetMinOnline,
RoomTargetMaxOnline: input.RoomTargetMaxOnline,
RobotStayMinMs: input.RobotStayMinMS,
RobotStayMaxMs: input.RobotStayMaxMS,
RobotReplaceMinMs: input.RobotReplaceMinMS,
RobotReplaceMaxMs: input.RobotReplaceMaxMS,
NormalGiftIntervalMs: input.NormalGiftIntervalMS,
NormalGiftIntervalMinMs: input.NormalGiftIntervalMinMS,
NormalGiftIntervalMaxMs: input.NormalGiftIntervalMaxMS,
GiftIds: append([]string(nil), input.GiftIDs...),
LuckyGiftIds: append([]string(nil), input.LuckyGiftIDs...),
SuperLuckyGiftIds: append([]string(nil), input.SuperLuckyGiftIDs...),
LuckyComboMin: input.LuckyComboMin,
LuckyComboMax: input.LuckyComboMax,
LuckyPauseMinMs: input.LuckyPauseMinMS,
LuckyPauseMaxMs: input.LuckyPauseMaxMS,
MaxGiftSenders: input.MaxGiftSenders,
UpdatedByAdminId: input.UpdatedByAdminID,
CreatedAtMs: input.CreatedAtMS,
UpdatedAtMs: input.UpdatedAtMS,
}
return out
}
func roomPinFromProto(input *roomv1.AdminRoomPin) RoomPin {
if input == nil {
return RoomPin{}
@ -700,6 +828,8 @@ func robotRoomFromProto(input *roomv1.AdminRobotRoom) RobotRoom {
Status: input.GetStatus(),
OwnerRobotUserID: input.GetOwnerRobotUserId(),
RobotUserIDs: append([]int64(nil), input.GetRobotUserIds()...),
ActiveRobotCount: input.GetActiveRobotCount(),
SeatCount: input.GetSeatCount(),
GiftRule: robotRoomGiftRuleFromProto(input.GetGiftRule()),
CreatedByAdminID: input.GetCreatedByAdminId(),
CreatedAtMS: input.GetCreatedAtMs(),
@ -719,6 +849,11 @@ func robotRoomGiftRuleFromProto(input *roomv1.AdminRobotRoomGiftRule) RobotRoomG
LuckyComboMax: input.GetLuckyComboMax(),
LuckyPauseMinMS: input.GetLuckyPauseMinMs(),
LuckyPauseMaxMS: input.GetLuckyPauseMaxMs(),
RobotStayMinMS: input.GetRobotStayMinMs(),
RobotStayMaxMS: input.GetRobotStayMaxMs(),
RobotReplaceMinMS: input.GetRobotReplaceMinMs(),
RobotReplaceMaxMS: input.GetRobotReplaceMaxMs(),
MaxGiftSenders: input.GetMaxGiftSenders(),
}
}
@ -731,6 +866,11 @@ func robotRoomGiftRuleToProto(input RobotRoomGiftRule) *roomv1.AdminRobotRoomGif
LuckyComboMax: input.LuckyComboMax,
LuckyPauseMinMs: input.LuckyPauseMinMS,
LuckyPauseMaxMs: input.LuckyPauseMaxMS,
RobotStayMinMs: input.RobotStayMinMS,
RobotStayMaxMs: input.RobotStayMaxMS,
RobotReplaceMinMs: input.RobotReplaceMinMS,
RobotReplaceMaxMs: input.RobotReplaceMaxMS,
MaxGiftSenders: input.MaxGiftSenders,
}
}

View File

@ -29,6 +29,7 @@ type Client interface {
UpsertGiftTypeConfig(ctx context.Context, req *walletv1.UpsertGiftTypeConfigRequest) (*walletv1.GiftTypeConfigResponse, error)
GrantResource(ctx context.Context, req *walletv1.GrantResourceRequest) (*walletv1.ResourceGrantResponse, error)
GrantResourceGroup(ctx context.Context, req *walletv1.GrantResourceGroupRequest) (*walletv1.ResourceGrantResponse, error)
EquipUserResource(ctx context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error)
ListResourceGrants(ctx context.Context, req *walletv1.ListResourceGrantsRequest) (*walletv1.ListResourceGrantsResponse, error)
ListResourceShopItems(ctx context.Context, req *walletv1.ListResourceShopItemsRequest) (*walletv1.ListResourceShopItemsResponse, error)
UpsertResourceShopItems(ctx context.Context, req *walletv1.UpsertResourceShopItemsRequest) (*walletv1.UpsertResourceShopItemsResponse, error)
@ -137,6 +138,10 @@ func (c *GRPCClient) GrantResourceGroup(ctx context.Context, req *walletv1.Grant
return c.client.GrantResourceGroup(ctx, req)
}
func (c *GRPCClient) EquipUserResource(ctx context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error) {
return c.client.EquipUserResource(ctx, req)
}
func (c *GRPCClient) ListResourceGrants(ctx context.Context, req *walletv1.ListResourceGrantsRequest) (*walletv1.ListResourceGrantsResponse, error) {
return c.client.ListResourceGrants(ctx, req)
}

View File

@ -97,7 +97,7 @@ type AppBanner struct {
CoverURL string `gorm:"size:1024;not null" json:"coverUrl"`
RoomSmallImageURL string `gorm:"column:room_small_image_url;size:1024;not null;default:'';comment:房间内小屏图 URL" json:"roomSmallImageUrl"`
BannerType string `gorm:"size:16;not null" json:"bannerType"`
DisplayScope string `gorm:"size:128;index:idx_admin_app_banners_display,not null;default:home;comment:显示范围,多选逗号分隔:首页、房间内或充值页" json:"displayScope"`
DisplayScope string `gorm:"size:128;index:idx_admin_app_banners_display,not null;default:home;comment:显示范围,多选逗号分隔:首页、房间内、充值页或我的页" json:"displayScope"`
Param string `gorm:"size:2048" json:"param"`
Status string `gorm:"size:24;index:idx_admin_app_banners_app_sort,not null;default:active" json:"status"`
Platform string `gorm:"size:24;index:idx_admin_app_banners_scope,not null" json:"platform"`

View File

@ -81,6 +81,10 @@ type applicationDTO struct {
WalletTransactionID string `json:"wallet_transaction_id"`
FailureReason string `json:"failure_reason"`
AppliedAtMS int64 `json:"applied_at_ms"`
ApprovedAtMS int64 `json:"approved_at_ms"`
ScoreStartMS int64 `json:"score_start_ms"`
ScoreEndMS int64 `json:"score_end_ms"`
ReviewedByAdminID int64 `json:"reviewed_by_admin_id"`
GrantedAtMS int64 `json:"granted_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
@ -201,6 +205,23 @@ func (h *Handler) ListApplications(c *gin.Context) {
response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}
func (h *Handler) ApproveApplication(c *gin.Context) {
applicationID := strings.TrimSpace(c.Param("application_id"))
resp, err := h.activity.ReviewAgencyOpeningApplication(c.Request.Context(), &activityv1.ReviewAgencyOpeningApplicationRequest{
Meta: h.meta(c),
ApplicationId: applicationID,
Action: "approve",
OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
item := applicationFromProto(resp.GetApplication())
shared.OperationLogWithResourceID(c, h.audit, "approve-agency-opening-application", "agency_opening_applications", item.ApplicationID, "success", "")
response.OK(c, item)
}
func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
return &activityv1.RequestMeta{
RequestId: middleware.CurrentRequestID(c),
@ -288,6 +309,10 @@ func applicationFromProto(item *activityv1.AgencyOpeningApplication) application
WalletTransactionID: item.GetWalletTransactionId(),
FailureReason: item.GetFailureReason(),
AppliedAtMS: item.GetAppliedAtMs(),
ApprovedAtMS: item.GetApprovedAtMs(),
ScoreStartMS: item.GetScoreStartMs(),
ScoreEndMS: item.GetScoreEndMs(),
ReviewedByAdminID: item.GetReviewedByAdminId(),
GrantedAtMS: item.GetGrantedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}

View File

@ -0,0 +1,102 @@
package agencyopening
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/middleware"
activityv1 "hyapp.local/api/proto/activity/v1"
"github.com/gin-gonic/gin"
)
func TestApproveApplicationForwardsReviewRequest(t *testing.T) {
activity := &fakeAgencyOpeningActivityClient{}
router := newAgencyOpeningHandlerTestRouter(New(activity, nil))
request := httptest.NewRequest(http.MethodPost, "/admin/activity/agency-opening/applications/application-1/approve", nil)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String())
}
if activity.lastReview == nil ||
activity.lastReview.GetApplicationId() != "application-1" ||
activity.lastReview.GetAction() != "approve" ||
activity.lastReview.GetOperatorAdminId() != 7 ||
activity.lastReview.GetMeta().GetAppCode() != "lalu" ||
activity.lastReview.GetMeta().GetCaller() != "admin-server" {
t.Fatalf("review request mismatch: %+v", activity.lastReview)
}
var envelope struct {
Code int `json:"code"`
Data struct {
ApplicationID string `json:"application_id"`
Status string `json:"status"`
ApprovedAtMS int64 `json:"approved_at_ms"`
ScoreStartMS int64 `json:"score_start_ms"`
ScoreEndMS int64 `json:"score_end_ms"`
ReviewedByAdminID int64 `json:"reviewed_by_admin_id"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil {
t.Fatalf("decode response: %v", err)
}
if envelope.Code != 0 ||
envelope.Data.ApplicationID != "application-1" ||
envelope.Data.Status != "approved" ||
envelope.Data.ApprovedAtMS != 2000 ||
envelope.Data.ScoreStartMS != 2000 ||
envelope.Data.ScoreEndMS != 2000+24*60*60*1000 ||
envelope.Data.ReviewedByAdminID != 7 {
t.Fatalf("approve response mismatch: %+v", envelope)
}
}
func newAgencyOpeningHandlerTestRouter(handler *Handler) *gin.Engine {
gin.SetMode(gin.TestMode)
router := gin.New()
router.Use(func(c *gin.Context) {
// 这里模拟后台鉴权中间件已经把 app_code、request_id 和操作者写入上下文,测试只关注接口参数透传。
c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), "lalu"))
c.Set(middleware.ContextRequestID, "agency-opening-handler-test")
c.Set(middleware.ContextUserID, uint(7))
c.Set(middleware.ContextUsername, "tester")
c.Next()
})
router.POST("/admin/activity/agency-opening/applications/:application_id/approve", handler.ApproveApplication)
return router
}
type fakeAgencyOpeningActivityClient struct {
activityclient.Client
lastReview *activityv1.ReviewAgencyOpeningApplicationRequest
}
func (f *fakeAgencyOpeningActivityClient) ReviewAgencyOpeningApplication(_ context.Context, req *activityv1.ReviewAgencyOpeningApplicationRequest) (*activityv1.ReviewAgencyOpeningApplicationResponse, error) {
f.lastReview = req
return &activityv1.ReviewAgencyOpeningApplicationResponse{
Application: &activityv1.AgencyOpeningApplication{
ApplicationId: req.GetApplicationId(),
CycleId: "cycle-1",
AgencyId: 8001,
AgencyOwnerUserId: 42001,
AgencyName: "Window Agency",
HostCount: 12,
Status: "approved",
AppliedAtMs: 1000,
ApprovedAtMs: 2000,
ScoreStartMs: 2000,
ScoreEndMs: 2000 + int64((24 * 60 * 60 * 1000)),
ReviewedByAdminId: req.GetOperatorAdminId(),
},
}, nil
}

View File

@ -16,4 +16,5 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
protected.PUT("/admin/activity/agency-opening/cycles/:cycle_id", middleware.RequirePermission("agency-opening:update"), h.UpdateCycle)
protected.PATCH("/admin/activity/agency-opening/cycles/:cycle_id/status", middleware.RequirePermission("agency-opening:update"), h.SetCycleStatus)
protected.GET("/admin/activity/agency-opening/applications", middleware.RequirePermission("agency-opening:view"), h.ListApplications)
protected.POST("/admin/activity/agency-opening/applications/:application_id/approve", middleware.RequirePermission("agency-opening:update"), h.ApproveApplication)
}

View File

@ -19,6 +19,7 @@ const (
bannerDisplayScopeHome = "home"
bannerDisplayScopeRoom = "room"
bannerDisplayScopeRecharge = "recharge"
bannerDisplayScopeMe = "me"
bannerStatusActive = "active"
bannerStatusDisabled = "disabled"
@ -27,7 +28,7 @@ const (
splashDefaultDisplayDurationMS = 3000
)
var bannerDisplayScopeOrder = []string{bannerDisplayScopeHome, bannerDisplayScopeRoom, bannerDisplayScopeRecharge}
var bannerDisplayScopeOrder = []string{bannerDisplayScopeHome, bannerDisplayScopeRoom, bannerDisplayScopeRecharge, bannerDisplayScopeMe}
type AppConfigService struct {
store *repository.Store
@ -963,6 +964,8 @@ func normalizeBannerDisplayScope(value string) string {
return bannerDisplayScopeRoom
case bannerDisplayScopeRecharge, "充值页":
return bannerDisplayScopeRecharge
case bannerDisplayScopeMe, "我的页", "我的":
return bannerDisplayScopeMe
default:
return value
}
@ -1017,7 +1020,7 @@ func validBannerDisplayScopes(scopes []string) bool {
return false
}
for _, scope := range scopes {
if scope != bannerDisplayScopeHome && scope != bannerDisplayScopeRoom && scope != bannerDisplayScopeRecharge {
if scope != bannerDisplayScopeHome && scope != bannerDisplayScopeRoom && scope != bannerDisplayScopeRecharge && scope != bannerDisplayScopeMe {
return false
}
}

View File

@ -94,12 +94,12 @@ func TestBannerModelFromRequestExpiresEndedActiveBanner(t *testing.T) {
}
func TestAppBannerFromModelReturnsDisplayScopes(t *testing.T) {
item := appBannerFromModel(bannerModel("home,room,recharge"))
item := appBannerFromModel(bannerModel("home,room,recharge,me"))
if item.DisplayScope != "home,room,recharge" {
if item.DisplayScope != "home,room,recharge,me" {
t.Fatalf("display scope should keep stored value: %+v", item)
}
if len(item.DisplayScopes) != 3 || item.DisplayScopes[0] != bannerDisplayScopeHome || item.DisplayScopes[1] != bannerDisplayScopeRoom || item.DisplayScopes[2] != bannerDisplayScopeRecharge {
if len(item.DisplayScopes) != 4 || item.DisplayScopes[0] != bannerDisplayScopeHome || item.DisplayScopes[1] != bannerDisplayScopeRoom || item.DisplayScopes[2] != bannerDisplayScopeRecharge || item.DisplayScopes[3] != bannerDisplayScopeMe {
t.Fatalf("display scopes mismatch: %+v", item.DisplayScopes)
}
}

View File

@ -4,8 +4,10 @@ import (
"strconv"
"strings"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/integration/gameclient"
"hyapp-admin-server/internal/integration/userclient"
"hyapp-admin-server/internal/integration/walletclient"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
gamev1 "hyapp.local/api/proto/game/v1"
@ -16,6 +18,8 @@ import (
type Handler struct {
game gameclient.Client
user userclient.Client
wallet walletclient.Client
activity activityclient.Client
audit shared.OperationLogger
robotProfiles RobotProfileSource
}

View File

@ -0,0 +1,188 @@
package gamemanagement
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
activityv1 "hyapp.local/api/proto/activity/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
)
const (
robotResourceDurationDays = 9999
robotResourceDurationMS = int64(robotResourceDurationDays) * int64(24*time.Hour/time.Millisecond)
robotMinDisplayLevel = 5
robotMaxDisplayLevel = 30
)
type robotAppearanceCatalog struct {
avatarFrames []*walletv1.Resource
vehicles []*walletv1.Resource
longBadges []*walletv1.Resource
shortBadges []*walletv1.Resource
}
func (h *Handler) initializeGeneratedRobotAppearance(ctx context.Context, requestID string, appCode string, actorUserID int64, userID int64) error {
if h.wallet == nil || h.activity == nil {
return fmt.Errorf("机器人装扮初始化依赖未配置")
}
catalog, err := h.loadRobotAppearanceCatalog(ctx, requestID, appCode)
if err != nil {
return err
}
avatarFrame := randomRobotResource(catalog.avatarFrames)
vehicle := randomRobotResource(catalog.vehicles)
longBadge := randomRobotResource(catalog.longBadges)
shortBadge := randomRobotResource(catalog.shortBadges)
if avatarFrame == nil || vehicle == nil || longBadge == nil || shortBadge == nil {
return fmt.Errorf("机器人装扮资源库不完整: 头像框=%d 座驾=%d 长徽章=%d 短徽章=%d", len(catalog.avatarFrames), len(catalog.vehicles), len(catalog.longBadges), len(catalog.shortBadges))
}
if err := h.grantAndEquipRobotResource(ctx, requestID, appCode, actorUserID, userID, "avatar_frame", avatarFrame.GetResourceId(), true); err != nil {
return err
}
if err := h.grantAndEquipRobotResource(ctx, requestID, appCode, actorUserID, userID, "vehicle", vehicle.GetResourceId(), true); err != nil {
return err
}
if err := h.grantAndEquipRobotResource(ctx, requestID, appCode, actorUserID, userID, "long_badge", longBadge.GetResourceId(), false); err != nil {
return err
}
if err := h.grantAndEquipRobotResource(ctx, requestID, appCode, actorUserID, userID, "short_badge", shortBadge.GetResourceId(), false); err != nil {
return err
}
// 财富和魅力等级由 activity-service 按等级规则写入账户、展示投影和等级奖励 job
// admin-server 只给出目标等级,避免绕过成长系统直接伪造资料卡字段。
if err := h.setRobotDisplayLevel(ctx, requestID, appCode, actorUserID, userID, "wealth", int32(robotMinDisplayLevel+randomRobotInt(robotMaxDisplayLevel-robotMinDisplayLevel+1))); err != nil {
return err
}
if err := h.setRobotDisplayLevel(ctx, requestID, appCode, actorUserID, userID, "charm", int32(robotMinDisplayLevel+randomRobotInt(robotMaxDisplayLevel-robotMinDisplayLevel+1))); err != nil {
return err
}
return nil
}
func (h *Handler) loadRobotAppearanceCatalog(ctx context.Context, requestID string, appCode string) (robotAppearanceCatalog, error) {
avatarFrames, err := h.listRobotResources(ctx, requestID, appCode, "avatar_frame")
if err != nil {
return robotAppearanceCatalog{}, err
}
vehicles, err := h.listRobotResources(ctx, requestID, appCode, "vehicle")
if err != nil {
return robotAppearanceCatalog{}, err
}
badges, err := h.listRobotResources(ctx, requestID, appCode, "badge")
if err != nil {
return robotAppearanceCatalog{}, err
}
catalog := robotAppearanceCatalog{avatarFrames: avatarFrames, vehicles: vehicles}
for _, badge := range badges {
switch robotBadgeForm(badge.GetMetadataJson()) {
case "strip", "long":
catalog.longBadges = append(catalog.longBadges, badge)
case "tile", "short":
catalog.shortBadges = append(catalog.shortBadges, badge)
}
}
return catalog, nil
}
func (h *Handler) listRobotResources(ctx context.Context, requestID string, appCode string, resourceType string) ([]*walletv1.Resource, error) {
resp, err := h.wallet.ListResources(ctx, &walletv1.ListResourcesRequest{
RequestId: requestID,
AppCode: appCode,
ResourceType: resourceType,
Page: 1,
PageSize: 500,
ActiveOnly: true,
})
if err != nil {
return nil, fmt.Errorf("读取机器人%s资源失败: %w", resourceType, err)
}
resources := make([]*walletv1.Resource, 0, len(resp.GetResources()))
for _, item := range resp.GetResources() {
if item != nil && item.GetResourceId() > 0 && item.GetStatus() == "active" {
resources = append(resources, item)
}
}
return resources, nil
}
func (h *Handler) grantAndEquipRobotResource(ctx context.Context, requestID string, appCode string, actorUserID int64, userID int64, slot string, resourceID int64, equip bool) error {
grant, err := h.wallet.GrantResource(ctx, &walletv1.GrantResourceRequest{
CommandId: fmt.Sprintf("game_robot_init:%s:%d:%s:%d", requestID, userID, slot, resourceID),
AppCode: appCode,
TargetUserId: userID,
ResourceId: resourceID,
Quantity: 1,
DurationMs: robotResourceDurationMS,
Reason: "game_robot_init",
OperatorUserId: actorUserID,
GrantSource: "game_robot_init",
})
if err != nil {
return fmt.Errorf("发放机器人%s失败: %w", slot, err)
}
if !equip {
return nil
}
entitlementID := ""
for _, item := range grant.GetGrant().GetItems() {
if item.GetResourceId() == resourceID {
entitlementID = item.GetEntitlementId()
break
}
}
if _, err := h.wallet.EquipUserResource(ctx, &walletv1.EquipUserResourceRequest{
RequestId: fmt.Sprintf("%s:%s:equip", requestID, slot),
AppCode: appCode,
UserId: userID,
ResourceId: resourceID,
EntitlementId: entitlementID,
}); err != nil {
return fmt.Errorf("装备机器人%s失败: %w", slot, err)
}
return nil
}
func (h *Handler) setRobotDisplayLevel(ctx context.Context, requestID string, appCode string, actorUserID int64, userID int64, track string, level int32) error {
_, err := h.activity.SetUserLevel(ctx, &activityv1.SetUserLevelRequest{
Meta: activityRequestMeta(requestID, appCode),
CommandId: fmt.Sprintf("game_robot_init:%s:%d:%s:%d", requestID, userID, track, level),
UserId: userID,
Track: track,
Level: level,
OperatorUserId: actorUserID,
Reason: "game_robot_init",
})
if err != nil {
return fmt.Errorf("设置机器人%s等级失败: %w", track, err)
}
return nil
}
func robotBadgeForm(metadataJSON string) string {
var payload map[string]any
if err := json.Unmarshal([]byte(strings.TrimSpace(metadataJSON)), &payload); err != nil {
return ""
}
value, _ := payload["badge_form"].(string)
return strings.ToLower(strings.TrimSpace(value))
}
func randomRobotResource(items []*walletv1.Resource) *walletv1.Resource {
if len(items) == 0 {
return nil
}
return items[randomRobotInt(int64(len(items)))]
}
func activityRequestMeta(requestID string, appCode string) *activityv1.RequestMeta {
return &activityv1.RequestMeta{
RequestId: requestID,
Caller: "admin-server",
SentAtMs: time.Now().UnixMilli(),
AppCode: appCode,
}
}

View File

@ -9,6 +9,9 @@ import (
"net/url"
"strings"
"unicode/utf8"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/integration/walletclient"
)
type RobotProfile struct {
@ -39,6 +42,15 @@ func WithRobotProfileSource(source RobotProfileSource) Option {
}
}
// WithRobotAppearanceServices 注入机器人装扮和等级初始化依赖robot-service 只登记机器人事实,
// 因此创建账号后的资源发放和成长等级必须由 admin-server 编排到对应 owner service。
func WithRobotAppearanceServices(wallet walletclient.Client, activity activityclient.Client) Option {
return func(h *Handler) {
h.wallet = wallet
h.activity = activity
}
}
func (s *likeiRobotProfileSource) RandomRobotProfiles(ctx context.Context, count int) ([]RobotProfile, error) {
if s == nil || s.db == nil {
return nil, fmt.Errorf("likei robot profile source is not configured")

View File

@ -8,10 +8,14 @@ import (
"strings"
"testing"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/integration/gameclient"
"hyapp-admin-server/internal/integration/userclient"
"hyapp-admin-server/internal/integration/walletclient"
"hyapp-admin-server/internal/middleware"
activityv1 "hyapp.local/api/proto/activity/v1"
gamev1 "hyapp.local/api/proto/game/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"github.com/gin-gonic/gin"
)
@ -59,6 +63,53 @@ func (c *fakeRobotGameClient) RegisterDiceRobots(_ context.Context, req *gamev1.
return &gamev1.RegisterDiceRobotsResponse{Robots: robots}, nil
}
type fakeRobotWalletClient struct {
walletclient.Client
listRequests []*walletv1.ListResourcesRequest
grants []string
equips []int64
}
func (c *fakeRobotWalletClient) ListResources(_ context.Context, req *walletv1.ListResourcesRequest) (*walletv1.ListResourcesResponse, error) {
c.listRequests = append(c.listRequests, req)
switch req.GetResourceType() {
case "avatar_frame":
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{{ResourceId: 101, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false}}}, nil
case "vehicle":
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{{ResourceId: 201, ResourceType: "vehicle", Status: "active", ManagerGrantEnabled: false}}}, nil
case "badge":
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{
{ResourceId: 301, ResourceType: "badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"strip"}`},
{ResourceId: 302, ResourceType: "badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"tile"}`},
}}, nil
default:
return &walletv1.ListResourcesResponse{}, nil
}
}
func (c *fakeRobotWalletClient) GrantResource(_ context.Context, req *walletv1.GrantResourceRequest) (*walletv1.ResourceGrantResponse, error) {
c.grants = append(c.grants, req.GetCommandId())
return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{Items: []*walletv1.ResourceGrantItem{{
ResourceId: req.GetResourceId(),
EntitlementId: "entitlement",
}}}}, nil
}
func (c *fakeRobotWalletClient) EquipUserResource(_ context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error) {
c.equips = append(c.equips, req.GetResourceId())
return &walletv1.EquipUserResourceResponse{}, nil
}
type fakeRobotActivityClient struct {
activityclient.Client
levels []*activityv1.SetUserLevelRequest
}
func (c *fakeRobotActivityClient) SetUserLevel(_ context.Context, req *activityv1.SetUserLevelRequest) (*activityv1.SetUserLevelResponse, error) {
c.levels = append(c.levels, req)
return &activityv1.SetUserLevelResponse{Status: "consumed", Track: req.GetTrack(), NewLevel: req.GetLevel()}, nil
}
func TestGenerateDiceRobotsUsesLikeiProfiles(t *testing.T) {
gin.SetMode(gin.TestMode)
profiles := &fakeRobotProfileSource{profiles: []RobotProfile{
@ -67,7 +118,9 @@ func TestGenerateDiceRobotsUsesLikeiProfiles(t *testing.T) {
}}
users := &fakeRobotUserClient{}
games := &fakeRobotGameClient{}
handler := New(games, users, nil, WithRobotProfileSource(profiles))
wallets := &fakeRobotWalletClient{}
activities := &fakeRobotActivityClient{}
handler := New(games, users, nil, WithRobotProfileSource(profiles), WithRobotAppearanceServices(wallets, activities))
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
@ -104,13 +157,29 @@ func TestGenerateDiceRobotsUsesLikeiProfiles(t *testing.T) {
if len(games.registered) != 2 || games.registered[0] != 9001 || games.registered[1] != 9002 {
t.Fatalf("registered user ids = %#v", games.registered)
}
if len(wallets.grants) != 8 || len(wallets.equips) != 4 || len(activities.levels) != 4 {
t.Fatalf("robot appearance init mismatch: grants=%d equips=%d levels=%d", len(wallets.grants), len(wallets.equips), len(activities.levels))
}
for _, req := range wallets.listRequests {
if req.GetManagerGrantOnly() {
t.Fatalf("robot appearance resource list must not require manager grant: %+v", req)
}
if !req.GetActiveOnly() {
t.Fatalf("robot appearance resource list must still require active resources: %+v", req)
}
}
for _, level := range activities.levels {
if level.GetLevel() < robotMinDisplayLevel || level.GetLevel() > robotMaxDisplayLevel {
t.Fatalf("robot level out of range: %+v", level)
}
}
}
func TestGenerateDiceRobotsFallsBackToRandomProfilesWhenLikeiMissing(t *testing.T) {
gin.SetMode(gin.TestMode)
users := &fakeRobotUserClient{}
games := &fakeRobotGameClient{}
handler := New(games, users, nil)
handler := New(games, users, nil, WithRobotAppearanceServices(&fakeRobotWalletClient{}, &fakeRobotActivityClient{}))
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)

View File

@ -232,6 +232,13 @@ func (h *Handler) GenerateDiceRobots(c *gin.Context) {
}
userIDs := make([]int64, 0, req.Count)
requestID := middleware.CurrentRequestID(c)
meta := requestMeta(c)
appCode := meta.GetAppCode()
if appCode == "" {
appCode = "lalu"
meta.AppCode = appCode
}
actorUserID := int64(middleware.CurrentUserID(c))
accountLanguage := robotAccountLanguage(req.NicknameLanguage)
for index := int32(0); index < req.Count; index++ {
profile := profiles[index]
@ -262,10 +269,14 @@ func (h *Handler) GenerateDiceRobots(c *gin.Context) {
response.BadRequest(c, "创建机器人用户失败: "+err.Error())
return
}
if err := h.initializeGeneratedRobotAppearance(c.Request.Context(), requestID, appCode, actorUserID, created.UserID); err != nil {
response.BadRequest(c, "初始化机器人装扮失败: "+err.Error())
return
}
userIDs = append(userIDs, created.UserID)
}
resp, err := h.game.RegisterDiceRobots(c.Request.Context(), &gamev1.RegisterDiceRobotsRequest{
Meta: requestMeta(c),
Meta: meta,
GameId: strings.TrimSpace(firstQuery(c, "gameId", "game_id")),
UserIds: userIDs,
})

View File

@ -3,6 +3,7 @@ package roomadmin
import (
"database/sql"
"errors"
"log/slog"
"strconv"
"strings"
@ -10,6 +11,7 @@ import (
"hyapp-admin-server/internal/integration/roomclient"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/repository"
"hyapp-admin-server/internal/response"
"github.com/gin-gonic/gin"
@ -20,8 +22,8 @@ type Handler struct {
audit shared.OperationLogger
}
func New(userDB *sql.DB, roomClient roomclient.Client, robotClient robotclient.Client, audit shared.OperationLogger) *Handler {
return &Handler{service: NewService(userDB, roomClient, robotClient), audit: audit}
func New(userDB *sql.DB, store *repository.Store, roomClient roomclient.Client, robotClient robotclient.Client, audit shared.OperationLogger) *Handler {
return &Handler{service: NewService(userDB, store, roomClient, robotClient), audit: audit}
}
func (h *Handler) ListRooms(c *gin.Context) {
@ -83,6 +85,10 @@ func (h *Handler) ListRobotRooms(c *gin.Context) {
query := parseRobotRoomListQuery(c)
items, total, err := h.service.ListRobotRooms(c.Request.Context(), query)
if err != nil {
slog.Error("roomadmin_list_robot_rooms_failed",
"request_id", middleware.CurrentRequestID(c),
"error", err.Error(),
)
response.ServerError(c, "获取机器人房间列表失败")
return
}
@ -121,6 +127,30 @@ func (h *Handler) StopRobotRoom(c *gin.Context) {
h.setRobotRoomStatus(c, "stopped", "stop-robot-room", "停止机器人房间失败")
}
func (h *Handler) GetHumanRoomRobotConfig(c *gin.Context) {
config, err := h.service.GetHumanRoomRobotConfig(c.Request.Context())
if err != nil {
response.ServerError(c, "获取真人房间机器人配置失败")
return
}
response.OK(c, config)
}
func (h *Handler) UpdateHumanRoomRobotConfig(c *gin.Context) {
var req updateHumanRoomRobotConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "真人房间机器人配置参数不正确")
return
}
config, err := h.service.UpdateHumanRoomRobotConfig(c.Request.Context(), req, shared.ActorFromContext(c))
if err != nil {
writeMutationError(c, err, "更新真人房间机器人配置失败")
return
}
writeRoomAuditLog(c, h.audit, "update-human-room-robot-config", "room_human_robot_configs", "human-room-robot", "success", "update human room robot config")
response.OK(c, config)
}
func (h *Handler) GetRoomConfig(c *gin.Context) {
config, err := h.service.GetRoomConfig(c.Request.Context())
if err != nil {
@ -130,6 +160,30 @@ func (h *Handler) GetRoomConfig(c *gin.Context) {
response.OK(c, config)
}
func (h *Handler) GetRoomWhitelistConfig(c *gin.Context) {
config, err := h.service.GetRoomWhitelistConfig(c.Request.Context())
if err != nil {
response.ServerError(c, "获取房间白名单失败")
return
}
response.OK(c, config)
}
func (h *Handler) UpdateRoomWhitelistConfig(c *gin.Context) {
var req updateRoomWhitelistRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "房间白名单参数不正确")
return
}
config, err := h.service.UpdateRoomWhitelistConfig(c.Request.Context(), req)
if err != nil {
writeMutationError(c, err, "更新房间白名单失败")
return
}
writeRoomAuditLog(c, h.audit, "update-room-whitelist", "admin_app_configs", "room-region-whitelist", "success", "update room whitelist")
response.OK(c, config)
}
func (h *Handler) UpdateRoomConfig(c *gin.Context) {
var req updateRoomConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -1,5 +1,12 @@
package roomadmin
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
type listQuery struct {
Page int
PageSize int
@ -26,20 +33,81 @@ type robotRoomListQuery struct {
}
type createRobotRoomRequest struct {
OwnerRobotUserID int64 `json:"ownerRobotUserId"`
CandidateRobotUserIDs []int64 `json:"candidateRobotUserIds"`
MinRobotCount int32 `json:"minRobotCount"`
MaxRobotCount int32 `json:"maxRobotCount"`
RoomName string `json:"roomName"`
RoomAvatar string `json:"roomAvatar"`
VisibleRegionID int64 `json:"visibleRegionId"`
GiftIDs []string `json:"giftIds"`
LuckyGiftIDs []string `json:"luckyGiftIds"`
NormalGiftIntervalMS int64 `json:"normalGiftIntervalMs"`
LuckyComboMin int64 `json:"luckyComboMin"`
LuckyComboMax int64 `json:"luckyComboMax"`
LuckyPauseMinMS int64 `json:"luckyPauseMinMs"`
LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"`
OwnerRobotUserID flexibleJSONInt64 `json:"ownerRobotUserId"`
CandidateRobotUserIDs flexibleJSONInt64Slice `json:"candidateRobotUserIds"`
MinRobotCount int32 `json:"minRobotCount"`
MaxRobotCount int32 `json:"maxRobotCount"`
RoomName string `json:"roomName"`
RoomAvatar string `json:"roomAvatar"`
VisibleRegionID int64 `json:"visibleRegionId"`
OwnerCountryCode string `json:"-"`
SeatCount int32 `json:"seatCount"`
GiftIDs []string `json:"giftIds"`
LuckyGiftIDs []string `json:"luckyGiftIds"`
NormalGiftIntervalMS int64 `json:"normalGiftIntervalMs"`
LuckyComboMin int64 `json:"luckyComboMin"`
LuckyComboMax int64 `json:"luckyComboMax"`
LuckyPauseMinMS int64 `json:"luckyPauseMinMs"`
LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"`
RobotStayMinMS int64 `json:"robotStayMinMs"`
RobotStayMaxMS int64 `json:"robotStayMaxMs"`
RobotReplaceMinMS int64 `json:"robotReplaceMinMs"`
RobotReplaceMaxMS int64 `json:"robotReplaceMaxMs"`
MaxGiftSenders int64 `json:"maxGiftSenders"`
}
type flexibleJSONInt64 int64
type flexibleJSONInt64Slice []int64
func (v *flexibleJSONInt64) UnmarshalJSON(data []byte) error {
// 机器人用户 ID 来自 int64前端必须按字符串提交才能避开 JS 安全整数上限;这里同时兼容旧脚本传数字。
value, err := parseFlexibleJSONInt64(data)
if err != nil {
return err
}
*v = flexibleJSONInt64(value)
return nil
}
func (values *flexibleJSONInt64Slice) UnmarshalJSON(data []byte) error {
// JSON 数组逐项走同一套 int64 解析,避免候选机器人列表里混入浮点或科学计数法。
var raw []json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
out := make([]int64, 0, len(raw))
for _, item := range raw {
value, err := parseFlexibleJSONInt64(item)
if err != nil {
return err
}
out = append(out, value)
}
*values = out
return nil
}
func parseFlexibleJSONInt64(data []byte) (int64, error) {
raw := strings.TrimSpace(string(data))
if raw == "" || raw == "null" {
return 0, nil
}
if strings.HasPrefix(raw, "\"") {
var text string
if err := json.Unmarshal(data, &text); err != nil {
return 0, err
}
raw = strings.TrimSpace(text)
}
if raw == "" {
return 0, nil
}
if strings.ContainsAny(raw, ".eE") {
// ID 没有小数语义,拒绝 float/scientific notation避免 18 位 ID 被中间层改写精度。
return 0, fmt.Errorf("integer id must not be decimal")
}
return strconv.ParseInt(raw, 10, 64)
}
type createRoomPinRequest struct {
@ -65,3 +133,52 @@ type updateRoomConfigRequest struct {
AllowedSeatCounts []int32 `json:"allowedSeatCounts"`
DefaultSeatCount int32 `json:"defaultSeatCount"`
}
type updateRoomWhitelistRequest struct {
UserIDs flexibleJSONInt64StringSlice `json:"userIds"`
}
type updateHumanRoomRobotConfigRequest struct {
Enabled bool `json:"enabled"`
CandidateRoomMaxOnline int32 `json:"candidateRoomMaxOnline"`
RoomFullStopOnline int32 `json:"roomFullStopOnline"`
RoomTargetMinOnline int32 `json:"roomTargetMinOnline"`
RoomTargetMaxOnline int32 `json:"roomTargetMaxOnline"`
RobotStayMinMS int64 `json:"robotStayMinMs"`
RobotStayMaxMS int64 `json:"robotStayMaxMs"`
RobotReplaceMinMS int64 `json:"robotReplaceMinMs"`
RobotReplaceMaxMS int64 `json:"robotReplaceMaxMs"`
NormalGiftIntervalMS int64 `json:"normalGiftIntervalMs"`
NormalGiftIntervalMinMS int64 `json:"normalGiftIntervalMinMs"`
NormalGiftIntervalMaxMS int64 `json:"normalGiftIntervalMaxMs"`
GiftIDs []string `json:"giftIds"`
LuckyGiftIDs []string `json:"luckyGiftIds"`
SuperLuckyGiftIDs []string `json:"superLuckyGiftIds"`
LuckyComboMin int64 `json:"luckyComboMin"`
LuckyComboMax int64 `json:"luckyComboMax"`
LuckyPauseMinMS int64 `json:"luckyPauseMinMs"`
LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"`
MaxGiftSenders int64 `json:"maxGiftSenders"`
}
type flexibleJSONInt64StringSlice []string
func (values *flexibleJSONInt64StringSlice) UnmarshalJSON(data []byte) error {
// 房间白名单 user_id 是 int64 标识API 合约统一按字符串输出;这里兼容旧脚本传数字但拒绝小数。
var raw []json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
out := make([]string, 0, len(raw))
for _, item := range raw {
value, err := parseFlexibleJSONInt64(item)
if err != nil {
return err
}
if value > 0 {
out = append(out, strconv.FormatInt(value, 10))
}
}
*values = out
return nil
}

View File

@ -11,6 +11,8 @@ import (
"hyapp-admin-server/internal/modules/shared"
)
const availableRoomRobotPageSize = int32(200)
type RobotRoom struct {
AppCode string `json:"appCode"`
RoomID string `json:"roomId"`
@ -24,6 +26,8 @@ type RobotRoom struct {
Owner RoomOwner `json:"owner"`
RobotUserIDs []string `json:"robotUserIds"`
Robots []RoomOwner `json:"robots"`
ActiveRobotCount int32 `json:"activeRobotCount"`
SeatCount int32 `json:"seatCount"`
GiftRule RobotGiftRule `json:"giftRule"`
CreatedByAdminID uint64 `json:"createdByAdminId"`
CreatedAtMS int64 `json:"createdAtMs"`
@ -38,6 +42,11 @@ type RobotGiftRule struct {
LuckyComboMax int64 `json:"luckyComboMax"`
LuckyPauseMinMS int64 `json:"luckyPauseMinMs"`
LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"`
RobotStayMinMS int64 `json:"robotStayMinMs"`
RobotStayMaxMS int64 `json:"robotStayMaxMs"`
RobotReplaceMinMS int64 `json:"robotReplaceMinMs"`
RobotReplaceMaxMS int64 `json:"robotReplaceMaxMs"`
MaxGiftSenders int64 `json:"maxGiftSenders"`
}
type AvailableRoomRobot struct {
@ -49,6 +58,33 @@ type AvailableRoomRobot struct {
User RoomOwner `json:"user"`
}
type HumanRoomRobotConfig struct {
AppCode string `json:"appCode"`
Enabled bool `json:"enabled"`
CandidateRoomMaxOnline int32 `json:"candidateRoomMaxOnline"`
RoomFullStopOnline int32 `json:"roomFullStopOnline"`
RoomTargetMinOnline int32 `json:"roomTargetMinOnline"`
RoomTargetMaxOnline int32 `json:"roomTargetMaxOnline"`
RobotStayMinMS int64 `json:"robotStayMinMs"`
RobotStayMaxMS int64 `json:"robotStayMaxMs"`
RobotReplaceMinMS int64 `json:"robotReplaceMinMs"`
RobotReplaceMaxMS int64 `json:"robotReplaceMaxMs"`
NormalGiftIntervalMS int64 `json:"normalGiftIntervalMs"`
NormalGiftIntervalMinMS int64 `json:"normalGiftIntervalMinMs"`
NormalGiftIntervalMaxMS int64 `json:"normalGiftIntervalMaxMs"`
GiftIDs []string `json:"giftIds"`
LuckyGiftIDs []string `json:"luckyGiftIds"`
SuperLuckyGiftIDs []string `json:"superLuckyGiftIds"`
LuckyComboMin int64 `json:"luckyComboMin"`
LuckyComboMax int64 `json:"luckyComboMax"`
LuckyPauseMinMS int64 `json:"luckyPauseMinMs"`
LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"`
MaxGiftSenders int64 `json:"maxGiftSenders"`
UpdatedByAdminID uint64 `json:"updatedByAdminId"`
CreatedAtMS int64 `json:"createdAtMs"`
UpdatedAtMS int64 `json:"updatedAtMs"`
}
func (s *Service) ListRobotRooms(ctx context.Context, query robotRoomListQuery) ([]RobotRoom, int64, error) {
if s.roomClient == nil {
return nil, 0, fmt.Errorf("room service client is not configured")
@ -87,15 +123,12 @@ func (s *Service) ListAvailableRoomRobots(ctx context.Context) ([]AvailableRoomR
if s.robotClient == nil || s.roomClient == nil {
return nil, fmt.Errorf("robot or room service client is not configured")
}
result, err := s.robotClient.ListGameRobots(ctx, robotclient.ListGameRobotsRequest{
Status: "active",
PageSize: 200,
})
robots, err := listAllActiveGameRobots(ctx, s.robotClient)
if err != nil {
return nil, err
}
userIDs := make([]int64, 0, len(result.Robots))
for _, item := range result.Robots {
userIDs := make([]int64, 0, len(robots))
for _, item := range robots {
if item.UserID > 0 {
userIDs = append(userIDs, item.UserID)
}
@ -110,7 +143,85 @@ func (s *Service) ListAvailableRoomRobots(ctx context.Context) ([]AvailableRoomR
if err != nil {
return nil, err
}
return availableRoomRobotsFromGamePool(result.Robots, available.AvailableUserIDs, owners), nil
return availableRoomRobotsFromGamePool(robots, available.AvailableUserIDs, owners), nil
}
func listAllActiveGameRobots(ctx context.Context, client robotclient.Client) ([]robotclient.Robot, error) {
if client == nil {
return nil, fmt.Errorf("robot service client is not configured")
}
cursor := ""
seenCursors := make(map[string]bool)
robots := make([]robotclient.Robot, 0, availableRoomRobotPageSize)
for {
// 机器人房间下拉要先拿到完整全站 active 机器人池,再统一交给 room-service 做占用过滤;
// 只取第一页会在测试服机器人数量超过 200 或第一页大多已占用时,把后续地区的新机器人挡在下拉外。
result, err := client.ListGameRobots(ctx, robotclient.ListGameRobotsRequest{
Status: "active",
PageSize: availableRoomRobotPageSize,
Cursor: cursor,
})
if err != nil {
return nil, err
}
robots = append(robots, result.Robots...)
next := strings.TrimSpace(result.NextCursor)
if next == "" {
return robots, nil
}
if seenCursors[next] {
return nil, fmt.Errorf("robot service returned repeated cursor %q", next)
}
seenCursors[next] = true
cursor = next
}
}
func (s *Service) GetHumanRoomRobotConfig(ctx context.Context) (HumanRoomRobotConfig, error) {
if s.roomClient == nil {
return HumanRoomRobotConfig{}, fmt.Errorf("room service client is not configured")
}
config, err := s.roomClient.GetHumanRoomRobotConfig(ctx)
if err != nil {
return HumanRoomRobotConfig{}, err
}
return s.humanRoomRobotConfigFromClient(ctx, config)
}
func (s *Service) UpdateHumanRoomRobotConfig(ctx context.Context, req updateHumanRoomRobotConfigRequest, actor shared.Actor) (HumanRoomRobotConfig, error) {
if s.roomClient == nil {
return HumanRoomRobotConfig{}, fmt.Errorf("room service client is not configured")
}
config := roomclient.HumanRoomRobotConfig{
Enabled: req.Enabled,
CandidateRoomMaxOnline: req.CandidateRoomMaxOnline,
RoomFullStopOnline: req.RoomFullStopOnline,
RoomTargetMinOnline: req.RoomTargetMinOnline,
RoomTargetMaxOnline: req.RoomTargetMaxOnline,
RobotStayMinMS: req.RobotStayMinMS,
RobotStayMaxMS: req.RobotStayMaxMS,
RobotReplaceMinMS: req.RobotReplaceMinMS,
RobotReplaceMaxMS: req.RobotReplaceMaxMS,
NormalGiftIntervalMS: req.NormalGiftIntervalMS,
NormalGiftIntervalMinMS: req.NormalGiftIntervalMinMS,
NormalGiftIntervalMaxMS: req.NormalGiftIntervalMaxMS,
GiftIDs: normalizeStringList(req.GiftIDs),
LuckyGiftIDs: normalizeStringList(req.LuckyGiftIDs),
SuperLuckyGiftIDs: normalizeStringList(req.SuperLuckyGiftIDs),
LuckyComboMin: req.LuckyComboMin,
LuckyComboMax: req.LuckyComboMax,
LuckyPauseMinMS: req.LuckyPauseMinMS,
LuckyPauseMaxMS: req.LuckyPauseMaxMS,
MaxGiftSenders: req.MaxGiftSenders,
}
saved, err := s.roomClient.UpdateHumanRoomRobotConfig(ctx, roomclient.UpdateHumanRoomRobotConfigRequest{
Config: config,
AdminID: uint64(actor.UserID),
})
if err != nil {
return HumanRoomRobotConfig{}, mapRoomClientError(err)
}
return s.humanRoomRobotConfigFromClient(ctx, saved)
}
func availableRoomRobotsFromGamePool(robots []robotclient.Robot, availableUserIDs []int64, owners map[int64]RoomOwner) []AvailableRoomRobot {
@ -135,6 +246,50 @@ func availableRoomRobotsFromGamePool(robots []robotclient.Robot, availableUserID
return items
}
func normalizeStringList(values []string) []string {
seen := make(map[string]bool, len(values))
out := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" || seen[value] {
continue
}
seen[value] = true
out = append(out, value)
}
return out
}
func (s *Service) humanRoomRobotConfigFromClient(_ context.Context, config roomclient.HumanRoomRobotConfig) (HumanRoomRobotConfig, error) {
out := HumanRoomRobotConfig{
AppCode: config.AppCode,
Enabled: config.Enabled,
CandidateRoomMaxOnline: config.CandidateRoomMaxOnline,
RoomFullStopOnline: config.RoomFullStopOnline,
RoomTargetMinOnline: config.RoomTargetMinOnline,
RoomTargetMaxOnline: config.RoomTargetMaxOnline,
RobotStayMinMS: config.RobotStayMinMS,
RobotStayMaxMS: config.RobotStayMaxMS,
RobotReplaceMinMS: config.RobotReplaceMinMS,
RobotReplaceMaxMS: config.RobotReplaceMaxMS,
NormalGiftIntervalMS: config.NormalGiftIntervalMS,
NormalGiftIntervalMinMS: config.NormalGiftIntervalMinMS,
NormalGiftIntervalMaxMS: config.NormalGiftIntervalMaxMS,
GiftIDs: append([]string(nil), config.GiftIDs...),
LuckyGiftIDs: append([]string(nil), config.LuckyGiftIDs...),
SuperLuckyGiftIDs: append([]string(nil), config.SuperLuckyGiftIDs...),
LuckyComboMin: config.LuckyComboMin,
LuckyComboMax: config.LuckyComboMax,
LuckyPauseMinMS: config.LuckyPauseMinMS,
LuckyPauseMaxMS: config.LuckyPauseMaxMS,
MaxGiftSenders: config.MaxGiftSenders,
UpdatedByAdminID: config.UpdatedByAdminID,
CreatedAtMS: config.CreatedAtMS,
UpdatedAtMS: config.UpdatedAtMS,
}
return out, nil
}
func (s *Service) CreateRobotRoom(ctx context.Context, req createRobotRoomRequest, actor shared.Actor) (RobotRoom, error) {
if s.roomClient == nil {
return RobotRoom{}, fmt.Errorf("room service client is not configured")
@ -148,13 +303,15 @@ func (s *Service) CreateRobotRoom(ctx context.Context, req createRobotRoomReques
return RobotRoom{}, err
}
created, err := s.roomClient.CreateRobotRoom(ctx, roomclient.CreateRobotRoomRequest{
OwnerRobotUserID: req.OwnerRobotUserID,
CandidateRobotUserIDs: req.CandidateRobotUserIDs,
OwnerRobotUserID: int64(req.OwnerRobotUserID),
CandidateRobotUserIDs: []int64(req.CandidateRobotUserIDs),
MinRobotCount: req.MinRobotCount,
MaxRobotCount: req.MaxRobotCount,
RoomName: req.RoomName,
RoomAvatar: req.RoomAvatar,
VisibleRegionID: req.VisibleRegionID,
OwnerCountryCode: req.OwnerCountryCode,
SeatCount: req.SeatCount,
GiftRule: roomclient.RobotRoomGiftRule{
GiftIDs: req.GiftIDs,
LuckyGiftIDs: req.LuckyGiftIDs,
@ -163,6 +320,11 @@ func (s *Service) CreateRobotRoom(ctx context.Context, req createRobotRoomReques
LuckyComboMax: req.LuckyComboMax,
LuckyPauseMinMS: req.LuckyPauseMinMS,
LuckyPauseMaxMS: req.LuckyPauseMaxMS,
RobotStayMinMS: req.RobotStayMinMS,
RobotStayMaxMS: req.RobotStayMaxMS,
RobotReplaceMinMS: req.RobotReplaceMinMS,
RobotReplaceMaxMS: req.RobotReplaceMaxMS,
MaxGiftSenders: req.MaxGiftSenders,
},
AdminID: uint64(actor.UserID),
})
@ -180,21 +342,28 @@ func (s *Service) applyOwnerProfileToRobotRoomRequest(ctx context.Context, req *
if s.userDB == nil {
return fmt.Errorf("user database is not configured")
}
owners, err := s.queryRoomOwners(ctx, []int64{req.OwnerRobotUserID})
ownerID := int64(req.OwnerRobotUserID)
owners, err := s.queryRoomOwners(ctx, []int64{ownerID})
if err != nil {
return err
}
owner, ok := owners[req.OwnerRobotUserID]
owner, ok := owners[ownerID]
if !ok {
return fmt.Errorf("%w: 房主机器人资料不存在", ErrInvalidArgument)
}
return applyOwnerProfileToRobotRoomRequest(req, owner)
}
func applyOwnerProfileToRobotRoomRequest(req *createRobotRoomRequest, owner RoomOwner) error {
roomName, roomAvatar, err := robotRoomProfileFromOwner(owner)
if err != nil {
return err
}
// 机器人房间对外展示必须跟随房主机器人资料,避免后台表单或脚本传入的自定义值让房间卡片和房主身份不一致。
// 机器人房间对外展示和区域归属必须跟随房主机器人资料,避免后台表单或脚本传入的自定义值让房间卡片、区域桶和国家筛选不一致。
req.RoomName = roomName
req.RoomAvatar = roomAvatar
req.VisibleRegionID = owner.VisibleRegionID
req.OwnerCountryCode = strings.ToUpper(strings.TrimSpace(owner.CountryCode))
return nil
}
@ -235,13 +404,21 @@ func (s *Service) SetRobotRoomStatus(ctx context.Context, roomID string, status
}
func normalizeCreateRobotRoomRequest(req createRobotRoomRequest) (createRobotRoomRequest, error) {
if req.OwnerRobotUserID <= 0 {
ownerID := int64(req.OwnerRobotUserID)
if ownerID <= 0 {
return req, fmt.Errorf("%w: 请选择房主机器人", ErrInvalidArgument)
}
if req.MinRobotCount <= 0 || req.MaxRobotCount < req.MinRobotCount {
return req, fmt.Errorf("%w: 机器人数量范围不正确", ErrInvalidArgument)
}
if len(req.CandidateRobotUserIDs) == 0 {
if req.SeatCount <= 0 {
req.SeatCount = 10
}
if !validRobotRoomSeatCount(req.SeatCount) {
return req, fmt.Errorf("%w: 麦位数不正确", ErrInvalidArgument)
}
candidateIDs := normalizeRobotRoomCandidateIDs([]int64(req.CandidateRobotUserIDs), ownerID)
if len(candidateIDs) == 0 {
return req, fmt.Errorf("%w: 请选择候选机器人", ErrInvalidArgument)
}
if len(req.GiftIDs) == 0 {
@ -259,9 +436,42 @@ func normalizeCreateRobotRoomRequest(req createRobotRoomRequest) (createRobotRoo
if req.LuckyPauseMinMS <= 0 || req.LuckyPauseMaxMS < req.LuckyPauseMinMS {
return req, fmt.Errorf("%w: 幸运礼物间隔范围不正确", ErrInvalidArgument)
}
if req.RobotStayMinMS < 0 || req.RobotStayMaxMS < req.RobotStayMinMS {
return req, fmt.Errorf("%w: 机器人停留时间范围不正确", ErrInvalidArgument)
}
if req.RobotReplaceMinMS < 0 || req.RobotReplaceMaxMS < req.RobotReplaceMinMS {
return req, fmt.Errorf("%w: 机器人补位时间范围不正确", ErrInvalidArgument)
}
if req.MaxGiftSenders < 0 {
return req, fmt.Errorf("%w: 同时送礼机器人数量不正确", ErrInvalidArgument)
}
req.CandidateRobotUserIDs = flexibleJSONInt64Slice(candidateIDs)
return req, nil
}
func validRobotRoomSeatCount(seatCount int32) bool {
switch seatCount {
case 10, 15, 20:
return true
default:
return false
}
}
func normalizeRobotRoomCandidateIDs(values []int64, ownerID int64) []int64 {
seen := make(map[int64]bool, len(values))
out := make([]int64, 0, len(values))
for _, value := range values {
// 房主由 room-service 单独并入机器人集合;候选列表只保留其他机器人,避免前端重复选择或旧脚本传入重复 ID。
if value <= 0 || value == ownerID || seen[value] {
continue
}
seen[value] = true
out = append(out, value)
}
return out
}
func robotRoomsFromClient(items []roomclient.RobotRoom) []RobotRoom {
out := make([]RobotRoom, 0, len(items))
for _, item := range items {
@ -285,6 +495,8 @@ func robotRoomFromClient(item roomclient.RobotRoom) RobotRoom {
Status: item.Status,
OwnerRobotUserID: strconv.FormatInt(item.OwnerRobotUserID, 10),
RobotUserIDs: robotUserIDs,
ActiveRobotCount: item.ActiveRobotCount,
SeatCount: item.SeatCount,
GiftRule: RobotGiftRule{
GiftIDs: item.GiftRule.GiftIDs,
LuckyGiftIDs: item.GiftRule.LuckyGiftIDs,
@ -293,6 +505,11 @@ func robotRoomFromClient(item roomclient.RobotRoom) RobotRoom {
LuckyComboMax: item.GiftRule.LuckyComboMax,
LuckyPauseMinMS: item.GiftRule.LuckyPauseMinMS,
LuckyPauseMaxMS: item.GiftRule.LuckyPauseMaxMS,
RobotStayMinMS: item.GiftRule.RobotStayMinMS,
RobotStayMaxMS: item.GiftRule.RobotStayMaxMS,
RobotReplaceMinMS: item.GiftRule.RobotReplaceMinMS,
RobotReplaceMaxMS: item.GiftRule.RobotReplaceMaxMS,
MaxGiftSenders: item.GiftRule.MaxGiftSenders,
},
CreatedByAdminID: item.CreatedByAdminID,
CreatedAtMS: item.CreatedAtMS,

View File

@ -17,11 +17,15 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
protected.DELETE("/admin/rooms/pins/:pin_id", middleware.RequirePermission("room-pin:cancel"), h.CancelRoomPin)
protected.GET("/admin/rooms/robot-rooms", middleware.RequirePermission("room-robot:view"), h.ListRobotRooms)
protected.GET("/admin/rooms/robot-rooms/available-robots", middleware.RequirePermission("room-robot:view"), h.ListAvailableRoomRobots)
protected.GET("/admin/rooms/robot-rooms/human-config", middleware.RequirePermission("room-robot:view"), h.GetHumanRoomRobotConfig)
protected.PUT("/admin/rooms/robot-rooms/human-config", middleware.RequirePermission("room-robot:update"), h.UpdateHumanRoomRobotConfig)
protected.POST("/admin/rooms/robot-rooms", middleware.RequirePermission("room-robot:create"), h.CreateRobotRoom)
protected.POST("/admin/rooms/robot-rooms/:room_id/start", middleware.RequirePermission("room-robot:update"), h.StartRobotRoom)
protected.POST("/admin/rooms/robot-rooms/:room_id/stop", middleware.RequirePermission("room-robot:update"), h.StopRobotRoom)
protected.GET("/admin/rooms/config", middleware.RequirePermission("room-config:view"), h.GetRoomConfig)
protected.PUT("/admin/rooms/config", middleware.RequirePermission("room-config:update"), h.UpdateRoomConfig)
protected.GET("/admin/rooms/whitelist", middleware.RequirePermission("room-whitelist:view"), h.GetRoomWhitelistConfig)
protected.PUT("/admin/rooms/whitelist", middleware.RequirePermission("room-whitelist:update"), h.UpdateRoomWhitelistConfig)
protected.PATCH("/admin/rooms/:room_id", middleware.RequirePermission("room:update"), h.UpdateRoom)
protected.DELETE("/admin/rooms/:room_id", middleware.RequirePermission("room:delete"), h.DeleteRoom)
}

View File

@ -15,6 +15,7 @@ import (
"hyapp-admin-server/internal/integration/robotclient"
"hyapp-admin-server/internal/integration/roomclient"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/repository"
)
var (
@ -24,15 +25,18 @@ var (
type Service struct {
userDB *sql.DB
store *repository.Store
roomClient roomclient.Client
robotClient robotclient.Client
}
type RoomOwner struct {
Avatar string `json:"avatar"`
DisplayUserID string `json:"displayUserId"`
UserID string `json:"userId"`
Username string `json:"username"`
Avatar string `json:"avatar"`
CountryCode string `json:"countryCode,omitempty"`
DisplayUserID string `json:"displayUserId"`
UserID string `json:"userId"`
Username string `json:"username"`
VisibleRegionID int64 `json:"visibleRegionId,omitempty"`
}
type Room struct {
@ -60,8 +64,8 @@ type Room struct {
VisibleRegionID int64 `json:"visibleRegionId"`
}
func NewService(userDB *sql.DB, roomClient roomclient.Client, robotClient robotclient.Client) *Service {
return &Service{userDB: userDB, roomClient: roomClient, robotClient: robotClient}
func NewService(userDB *sql.DB, store *repository.Store, roomClient roomclient.Client, robotClient robotclient.Client) *Service {
return &Service{userDB: userDB, store: store, roomClient: roomClient, robotClient: robotClient}
}
func (s *Service) ListRooms(ctx context.Context, query listQuery) ([]Room, int64, error) {
@ -463,7 +467,9 @@ func (s *Service) queryRoomOwners(ctx context.Context, ownerIDs []int64) (map[in
SELECT user_id,
current_display_user_id,
COALESCE(username, ''),
COALESCE(avatar, '')
COALESCE(avatar, ''),
COALESCE(country, ''),
COALESCE(region_id, 0)
FROM users
WHERE app_code = ? AND user_id IN (`+placeholders(len(ownerIDs))+`)
`, args...)
@ -475,7 +481,7 @@ func (s *Service) queryRoomOwners(ctx context.Context, ownerIDs []int64) (map[in
for rows.Next() {
var userID int64
var owner RoomOwner
if err := rows.Scan(&userID, &owner.DisplayUserID, &owner.Username, &owner.Avatar); err != nil {
if err := rows.Scan(&userID, &owner.DisplayUserID, &owner.Username, &owner.Avatar, &owner.CountryCode, &owner.VisibleRegionID); err != nil {
return nil, err
}
owner.UserID = strconv.FormatInt(userID, 10)

View File

@ -1,9 +1,16 @@
package roomadmin
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"hyapp-admin-server/internal/integration/robotclient"
)
@ -77,6 +84,115 @@ func TestRobotRoomProfileFromOwnerRequiresNameAndAvatar(t *testing.T) {
}
}
func TestApplyOwnerProfileToRobotRoomRequestFollowsOwnerRegionAndCountry(t *testing.T) {
req := createRobotRoomRequest{
RoomName: "manual name",
RoomAvatar: "https://cdn.example.com/manual.png",
VisibleRegionID: 999,
OwnerCountryCode: "XX",
}
err := applyOwnerProfileToRobotRoomRequest(&req, RoomOwner{
Username: " Robot Owner ",
Avatar: " https://cdn.example.com/owner.png ",
CountryCode: " ae ",
VisibleRegionID: 686,
})
if err != nil {
t.Fatalf("apply owner profile failed: %v", err)
}
if req.RoomName != "Robot Owner" || req.RoomAvatar != "https://cdn.example.com/owner.png" {
t.Fatalf("owner display profile mismatch: name=%q avatar=%q", req.RoomName, req.RoomAvatar)
}
if req.VisibleRegionID != 686 || req.OwnerCountryCode != "AE" {
t.Fatalf("owner region or country mismatch: region=%d country=%q", req.VisibleRegionID, req.OwnerCountryCode)
}
}
func TestCreateRobotRoomRequestAcceptsStringInt64IDs(t *testing.T) {
var req createRobotRoomRequest
if err := json.Unmarshal([]byte(`{
"ownerRobotUserId":"325379237278126080",
"candidateRobotUserIds":["325379237278126080","325455441691676672","325455441691676672"],
"minRobotCount":2,
"maxRobotCount":8,
"giftIds":["84"],
"luckyGiftIds":["28"],
"normalGiftIntervalMs":10000,
"luckyComboMin":100,
"luckyComboMax":10000,
"luckyPauseMinMs":5000,
"luckyPauseMaxMs":20000
}`), &req); err != nil {
t.Fatalf("robot room request should accept string int64 ids: %v", err)
}
normalized, err := normalizeCreateRobotRoomRequest(req)
if err != nil {
t.Fatalf("robot room request should normalize: %v", err)
}
if got := int64(normalized.OwnerRobotUserID); got != 325379237278126080 {
t.Fatalf("owner id mismatch: got %d", got)
}
if got := []int64(normalized.CandidateRobotUserIDs); len(got) != 1 || got[0] != 325455441691676672 {
t.Fatalf("candidate ids should drop owner and duplicates, got %+v", got)
}
}
func TestCreateRobotRoomRequestBindsStringInt64IDsFromHTTP(t *testing.T) {
gin.SetMode(gin.TestMode)
body := `{
"ownerRobotUserId":"325379237278126080",
"candidateRobotUserIds":["325455441691676672"],
"minRobotCount":2,
"maxRobotCount":8,
"giftIds":["84"],
"luckyGiftIds":["28"],
"normalGiftIntervalMs":10000,
"luckyComboMin":100,
"luckyComboMax":10000,
"luckyPauseMinMs":5000,
"luckyPauseMaxMs":20000
}`
req := httptest.NewRequest(http.MethodPost, "/admin/rooms/robot-rooms", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(rec)
ctx.Request = req
var payload createRobotRoomRequest
if err := ctx.ShouldBindJSON(&payload); err != nil {
t.Fatalf("gin binding should accept string int64 ids: %v", err)
}
if got := int64(payload.OwnerRobotUserID); got != 325379237278126080 {
t.Fatalf("owner id mismatch: got %d", got)
}
if got := []int64(payload.CandidateRobotUserIDs); len(got) != 1 || got[0] != 325455441691676672 {
t.Fatalf("candidate ids mismatch: %+v", got)
}
}
func TestNormalizeRoomWhitelistUserIDsKeepsStringInt64Order(t *testing.T) {
got, err := normalizeRoomWhitelistUserIDs([]string{"325379237278126080", "1001", "1001"})
if err != nil {
t.Fatalf("normalize room whitelist failed: %v", err)
}
want := []string{"1001", "325379237278126080"}
if len(got) != len(want) {
t.Fatalf("whitelist length mismatch: got %+v want %+v", got, want)
}
for index := range want {
if got[index] != want[index] {
t.Fatalf("whitelist id %d mismatch: got %+v want %+v", index, got, want)
}
}
}
func TestNormalizeRoomWhitelistUserIDsRejectsInvalidID(t *testing.T) {
if _, err := normalizeRoomWhitelistUserIDs([]string{"1.2"}); !errors.Is(err, ErrInvalidArgument) {
t.Fatalf("invalid id should return ErrInvalidArgument, got %v", err)
}
}
func TestAvailableRoomRobotsFromGamePoolKeepsOnlyRoomAvailableUsers(t *testing.T) {
robots := []robotclient.Robot{
{UserID: 101, Status: "active", LastUsedAtMS: 1000, UsedCount: 2},
@ -99,3 +215,50 @@ func TestAvailableRoomRobotsFromGamePoolKeepsOnlyRoomAvailableUsers(t *testing.T
t.Fatalf("second available robot mismatch: %+v", items[1])
}
}
func TestListAllActiveGameRobotsFollowsCursor(t *testing.T) {
client := &pagedRobotClient{
pages: map[string]robotclient.ListGameRobotsResult{
"": {
Robots: []robotclient.Robot{{UserID: 101}, {UserID: 102}},
NextCursor: "102",
},
"102": {
Robots: []robotclient.Robot{{UserID: 201}},
},
},
}
robots, err := listAllActiveGameRobots(context.Background(), client)
if err != nil {
t.Fatalf("list all active game robots failed: %v", err)
}
if len(robots) != 3 || robots[0].UserID != 101 || robots[1].UserID != 102 || robots[2].UserID != 201 {
t.Fatalf("robots should include every cursor page, got %+v", robots)
}
if len(client.requests) != 2 {
t.Fatalf("expected two page requests, got %+v", client.requests)
}
if client.requests[0].Cursor != "" || client.requests[1].Cursor != "102" {
t.Fatalf("cursor chain mismatch: %+v", client.requests)
}
for _, req := range client.requests {
if req.Status != "active" || req.PageSize != availableRoomRobotPageSize {
t.Fatalf("request should use active status and page size %d, got %+v", availableRoomRobotPageSize, req)
}
}
}
type pagedRobotClient struct {
pages map[string]robotclient.ListGameRobotsResult
requests []robotclient.ListGameRobotsRequest
}
func (c *pagedRobotClient) ListGameRobots(_ context.Context, req robotclient.ListGameRobotsRequest) (robotclient.ListGameRobotsResult, error) {
c.requests = append(c.requests, req)
return c.pages[req.Cursor], nil
}
func (c *pagedRobotClient) ListRoomRobots(context.Context, robotclient.ListRoomRobotsRequest) (robotclient.ListRoomRobotsResult, error) {
return robotclient.ListRoomRobotsResult{}, errors.New("not used")
}

View File

@ -0,0 +1,112 @@
package roomadmin
import (
"context"
"encoding/json"
"errors"
"fmt"
"slices"
"strconv"
"strings"
"gorm.io/gorm"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/model"
)
const roomRegionWhitelistGroup = "room-region-whitelist"
type RoomWhitelistConfig struct {
UserIDs []string `json:"userIds"`
UpdatedAtMS int64 `json:"updatedAtMs"`
}
type roomRegionWhitelistValue struct {
UserIDs []string `json:"user_ids"`
}
// GetRoomWhitelistConfig 读取当前 app 的房间区域白名单;缺行时返回空集合,表示所有用户仍受区域限制。
func (s *Service) GetRoomWhitelistConfig(ctx context.Context) (RoomWhitelistConfig, error) {
if s.store == nil {
return RoomWhitelistConfig{}, fmt.Errorf("admin store is not configured")
}
item, err := s.store.GetAppConfig(roomRegionWhitelistGroup, appctx.FromContext(ctx))
if errors.Is(err, gorm.ErrRecordNotFound) {
return RoomWhitelistConfig{UserIDs: []string{}}, nil
}
if err != nil {
return RoomWhitelistConfig{}, err
}
userIDs, err := roomWhitelistIDsFromValue(item.Value)
if err != nil {
return RoomWhitelistConfig{}, err
}
return RoomWhitelistConfig{UserIDs: userIDs, UpdatedAtMS: item.UpdatedAtMS}, nil
}
// UpdateRoomWhitelistConfig 覆盖当前 app 的房间区域白名单gateway 会在最多五分钟内通过 Redis 缓存刷新结果。
func (s *Service) UpdateRoomWhitelistConfig(ctx context.Context, req updateRoomWhitelistRequest) (RoomWhitelistConfig, error) {
if s.store == nil {
return RoomWhitelistConfig{}, fmt.Errorf("admin store is not configured")
}
userIDs, err := normalizeRoomWhitelistUserIDs(req.UserIDs)
if err != nil {
return RoomWhitelistConfig{}, err
}
payload, err := json.Marshal(roomRegionWhitelistValue{UserIDs: userIDs})
if err != nil {
return RoomWhitelistConfig{}, err
}
item := model.AppConfig{
Group: roomRegionWhitelistGroup,
Key: appctx.FromContext(ctx),
Value: string(payload),
Description: "房间区域白名单用户,命中后 App 房间列表不按用户 region_id 过滤",
}
if err := s.store.UpsertAppConfigs([]model.AppConfig{item}); err != nil {
return RoomWhitelistConfig{}, err
}
return s.GetRoomWhitelistConfig(ctx)
}
func roomWhitelistIDsFromValue(raw string) ([]string, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return []string{}, nil
}
var payload roomRegionWhitelistValue
if strings.HasPrefix(raw, "[") {
if err := json.Unmarshal([]byte(raw), &payload.UserIDs); err != nil {
return nil, err
}
} else if err := json.Unmarshal([]byte(raw), &payload); err != nil {
return nil, err
}
return normalizeRoomWhitelistUserIDs(payload.UserIDs)
}
func normalizeRoomWhitelistUserIDs(values []string) ([]string, error) {
seen := make(map[int64]struct{}, len(values))
out := make([]int64, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" {
continue
}
id, err := strconv.ParseInt(value, 10, 64)
if err != nil || id <= 0 {
return nil, fmt.Errorf("%w: 用户 ID 不正确", ErrInvalidArgument)
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
out = append(out, id)
}
slices.Sort(out)
result := make([]string, 0, len(out))
for _, id := range out {
result = append(result, strconv.FormatInt(id, 10))
}
return result, nil
}

View File

@ -0,0 +1,483 @@
package wheel
import (
"context"
"fmt"
"math"
"strings"
"time"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
activityv1 "hyapp.local/api/proto/activity/v1"
"github.com/gin-gonic/gin"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const defaultWheelID = "default"
var wheelTierCounts = map[string]int{
"classic": 9,
"luxury": 8,
"advanced": 12,
}
type Handler struct {
activity activityclient.Client
requestTimeout time.Duration
audit shared.OperationLogger
}
func New(activity activityclient.Client, requestTimeout time.Duration, audit shared.OperationLogger) *Handler {
if requestTimeout <= 0 {
requestTimeout = 3 * time.Second
}
return &Handler{activity: activity, requestTimeout: requestTimeout, audit: audit}
}
type configRequest struct {
WheelID string `json:"wheel_id"`
Enabled bool `json:"enabled"`
DrawPriceCoins int64 `json:"draw_price_coins"`
TargetRTPPercent float64 `json:"target_rtp_percent"`
PoolRatePercent float64 `json:"pool_rate_percent"`
SettlementWindowDraws int64 `json:"settlement_window_draws"`
InitialPoolCoins int64 `json:"initial_pool_coins"`
PoolReserveCoins int64 `json:"pool_reserve_coins"`
MaxSingleRTPPayout int64 `json:"max_single_rtp_payout"`
EffectiveFromMS int64 `json:"effective_from_ms"`
Tiers []tierDTO `json:"tiers"`
}
type configDTO struct {
configRequest
AppCode string `json:"app_code"`
RuleVersion int64 `json:"rule_version"`
CreatedByAdminID int64 `json:"created_by_admin_id"`
CreatedAtMS int64 `json:"created_at_ms"`
}
type tierDTO struct {
TierID string `json:"tier_id"`
DisplayName string `json:"display_name"`
RewardType string `json:"reward_type"`
RewardID string `json:"reward_id"`
RewardCount int64 `json:"reward_count"`
RewardCoins int64 `json:"reward_coins"`
RTPValueCoins int64 `json:"rtp_value_coins"`
ProbabilityPercent float64 `json:"probability_percent"`
Enabled bool `json:"enabled"`
MetadataJSON string `json:"metadata_json"`
}
type drawDTO struct {
DrawID string `json:"draw_id"`
DrawIDs []string `json:"draw_ids"`
CommandID string `json:"command_id"`
WheelID string `json:"wheel_id"`
RuleVersion int64 `json:"rule_version"`
SelectedTierID string `json:"selected_tier_id"`
RewardType string `json:"reward_type"`
RewardID string `json:"reward_id"`
RewardCount int64 `json:"reward_count"`
RewardCoins int64 `json:"reward_coins"`
RTPValueCoins int64 `json:"rtp_value_coins"`
RewardStatus string `json:"reward_status"`
RTPWindowIndex int64 `json:"rtp_window_index"`
ActualRTPPPM int64 `json:"actual_rtp_ppm"`
CreatedAtMS int64 `json:"created_at_ms"`
WalletTransactionID string `json:"wallet_transaction_id"`
CoinBalanceAfter int64 `json:"coin_balance_after"`
MetadataJSON string `json:"metadata_json"`
}
type drawSummaryDTO struct {
WheelID string `json:"wheel_id"`
TotalDraws int64 `json:"total_draws"`
UniqueUsers int64 `json:"unique_users"`
TotalSpentCoins int64 `json:"total_spent_coins"`
TotalRTPValueCoins int64 `json:"total_rtp_value_coins"`
ActualRTPPPM int64 `json:"actual_rtp_ppm"`
PendingDraws int64 `json:"pending_draws"`
GrantedDraws int64 `json:"granted_draws"`
FailedDraws int64 `json:"failed_draws"`
}
func (h *Handler) GetConfig(c *gin.Context) {
wheelID := normalizeWheelID(firstQuery(c, "wheel_id", "wheelId"))
ctx, cancel := h.activityContext(c)
defer cancel()
resp, err := h.activity.GetWheelConfig(ctx, &activityv1.GetWheelConfigRequest{Meta: h.meta(c), WheelId: wheelID})
if err != nil {
if status.Code(err) == codes.NotFound {
response.OK(c, defaultConfigDTO(wheelID))
return
}
response.ServerError(c, "获取转盘配置失败")
return
}
response.OK(c, configFromProto(resp.GetConfig()))
}
func (h *Handler) UpsertConfig(c *gin.Context) {
var req configRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "转盘配置参数不正确")
return
}
if req.WheelID == "" {
req.WheelID = firstQuery(c, "wheel_id", "wheelId")
}
if err := validateConfigRequest(req); err != nil {
response.BadRequest(c, err.Error())
return
}
ctx, cancel := h.activityContext(c)
defer cancel()
resp, err := h.activity.UpsertWheelConfig(ctx, &activityv1.UpsertWheelConfigRequest{
Meta: h.meta(c),
Config: configToProto(req),
OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
item := configFromProto(resp.GetConfig())
shared.OperationLogWithResourceID(c, h.audit, "upsert-wheel-config", "wheel_rule_versions", item.WheelID, "success", "")
response.OK(c, item)
}
func (h *Handler) ListDraws(c *gin.Context) {
options := shared.ListOptions(c)
ctx, cancel := h.activityContext(c)
defer cancel()
resp, err := h.activity.ListWheelDraws(ctx, &activityv1.ListWheelDrawsRequest{
Meta: h.meta(c),
WheelId: normalizeWheelID(firstQuery(c, "wheel_id", "wheelId")),
UserId: parseOptionalInt64(firstQuery(c, "user_id", "userId")),
Status: strings.TrimSpace(options.Status),
Page: int32(options.Page),
PageSize: int32(options.PageSize),
})
if err != nil {
response.ServerError(c, "获取转盘抽奖记录失败")
return
}
items := make([]drawDTO, 0, len(resp.GetDraws()))
for _, draw := range resp.GetDraws() {
items = append(items, drawFromProto(draw))
}
response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}
func (h *Handler) GetDrawSummary(c *gin.Context) {
ctx, cancel := h.activityContext(c)
defer cancel()
resp, err := h.activity.GetWheelDrawSummary(ctx, &activityv1.GetWheelDrawSummaryRequest{
Meta: h.meta(c),
WheelId: normalizeWheelID(firstQuery(c, "wheel_id", "wheelId")),
UserId: parseOptionalInt64(firstQuery(c, "user_id", "userId")),
Status: strings.TrimSpace(c.Query("status")),
})
if err != nil {
response.ServerError(c, "获取转盘抽奖汇总失败")
return
}
response.OK(c, drawSummaryFromProto(resp.GetSummary()))
}
func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
return &activityv1.RequestMeta{
RequestId: middleware.CurrentRequestID(c),
Caller: "admin-server",
AppCode: appctx.FromContext(c.Request.Context()),
SentAtMs: time.Now().UTC().UnixMilli(),
}
}
func (h *Handler) activityContext(c *gin.Context) (context.Context, context.CancelFunc) {
return context.WithTimeout(c.Request.Context(), h.requestTimeout)
}
func validateConfigRequest(req configRequest) error {
if normalizeWheelID(req.WheelID) == "" {
return fmt.Errorf("wheel_id 不能为空")
}
expectedCount := expectedWheelTierCount(req.WheelID)
// 转盘前端布局是固定格子数classic=9、luxury=8、advanced=12。这里按池子强校验
// 避免运营导入资源组后少格或多格,导致 H5 动画序列和后端奖品配置无法一一对应。
if len(req.Tiers) != expectedCount {
return fmt.Errorf("%s 转盘必须配置 %d 个奖品档位", normalizeWheelID(req.WheelID), expectedCount)
}
var totalProbability float64
for _, tier := range req.Tiers {
rewardType := normalizeRewardType(tier.RewardType)
if rewardType == "" {
return fmt.Errorf("奖品类型不能为空")
}
totalProbability += tier.ProbabilityPercent
switch rewardType {
case "coin":
if tier.RewardCoins <= 0 {
return fmt.Errorf("金币档 reward_coins 必须大于 0")
}
case "gift":
if strings.TrimSpace(tier.RewardID) == "" {
return fmt.Errorf("礼物档 reward_id 不能为空")
}
case "prop", "dress":
if strings.TrimSpace(tier.RewardID) == "" {
return fmt.Errorf("道具或装扮档 reward_id 不能为空")
}
default:
return fmt.Errorf("奖品类型只支持 coin、gift、prop、dress")
}
// 道具和装扮用于背包展示,不进入 RTP 支出;真实清零在 configToProto 里执行,
// 这里保留运营输入兼容性,防止旧配置带值时直接无法打开保存。
if tier.ProbabilityPercent < 0 {
return fmt.Errorf("奖品概率不能为负数")
}
}
if math.Abs(totalProbability-100) > 0.000001 {
return fmt.Errorf("奖品档位概率合计必须等于 100")
}
return nil
}
func configToProto(req configRequest) *activityv1.WheelRuleConfig {
tiers := make([]*activityv1.WheelPrizeTier, 0, len(req.Tiers))
for index, tier := range req.Tiers {
rewardType := normalizeRewardType(tier.RewardType)
rtpValue := tier.RTPValueCoins
rewardCoins := tier.RewardCoins
if rewardType == "coin" && rtpValue <= 0 {
rtpValue = rewardCoins
}
if rewardType == "prop" || rewardType == "dress" {
rtpValue = 0
}
tiers = append(tiers, &activityv1.WheelPrizeTier{
TierId: generatedTierID(tier.TierID, rewardType, index),
DisplayName: strings.TrimSpace(tier.DisplayName),
RewardType: rewardType,
RewardId: strings.TrimSpace(tier.RewardID),
RewardCount: tier.RewardCount,
RewardCoins: rewardCoins,
RtpValueCoins: rtpValue,
WeightPpm: percentToPPM(tier.ProbabilityPercent),
Enabled: tier.Enabled,
MetadataJson: normalizeMetadataJSON(tier.MetadataJSON),
})
}
return &activityv1.WheelRuleConfig{
WheelId: normalizeWheelID(req.WheelID),
Enabled: req.Enabled,
DrawPriceCoins: req.DrawPriceCoins,
TargetRtpPpm: percentToPPM(req.TargetRTPPercent),
PoolRatePpm: percentToPPM(req.PoolRatePercent),
SettlementWindowDraws: req.SettlementWindowDraws,
InitialPoolCoins: req.InitialPoolCoins,
PoolReserveCoins: req.PoolReserveCoins,
MaxSingleRtpPayout: req.MaxSingleRTPPayout,
EffectiveFromMs: req.EffectiveFromMS,
Tiers: tiers,
}
}
func configFromProto(config *activityv1.WheelRuleConfig) configDTO {
if config == nil {
return configDTO{}
}
tiers := make([]tierDTO, 0, len(config.GetTiers()))
for _, tier := range config.GetTiers() {
tiers = append(tiers, tierDTO{
TierID: tier.GetTierId(),
DisplayName: tier.GetDisplayName(),
RewardType: tier.GetRewardType(),
RewardID: tier.GetRewardId(),
RewardCount: tier.GetRewardCount(),
RewardCoins: tier.GetRewardCoins(),
RTPValueCoins: tier.GetRtpValueCoins(),
ProbabilityPercent: ppmToPercent(tier.GetWeightPpm()),
Enabled: tier.GetEnabled(),
MetadataJSON: tier.GetMetadataJson(),
})
}
return configDTO{
AppCode: config.GetAppCode(),
RuleVersion: config.GetRuleVersion(),
CreatedByAdminID: config.GetCreatedByAdminId(),
CreatedAtMS: config.GetCreatedAtMs(),
configRequest: configRequest{
WheelID: normalizeWheelID(config.GetWheelId()),
Enabled: config.GetEnabled(),
DrawPriceCoins: config.GetDrawPriceCoins(),
TargetRTPPercent: ppmToPercent(config.GetTargetRtpPpm()),
PoolRatePercent: ppmToPercent(config.GetPoolRatePpm()),
SettlementWindowDraws: config.GetSettlementWindowDraws(),
InitialPoolCoins: config.GetInitialPoolCoins(),
PoolReserveCoins: config.GetPoolReserveCoins(),
MaxSingleRTPPayout: config.GetMaxSingleRtpPayout(),
EffectiveFromMS: config.GetEffectiveFromMs(),
Tiers: tiers,
},
}
}
func defaultConfigDTO(wheelID string) configDTO {
wheelID = normalizeWheelID(wheelID)
// 没有保存过配置时只返回基础参数和空档位。奖品档位必须从资源组导入,
// 不能用占位奖品冒充真实配置,否则运营会误以为当前池子已经可发布。
return configDTO{configRequest: configRequest{
WheelID: wheelID,
Enabled: false,
DrawPriceCoins: 10_000,
TargetRTPPercent: 90,
PoolRatePercent: 90,
SettlementWindowDraws: 1000,
InitialPoolCoins: 0,
PoolReserveCoins: 0,
MaxSingleRTPPayout: 0,
Tiers: []tierDTO{},
}}
}
func expectedWheelTierCount(wheelID string) int {
if count, ok := wheelTierCounts[normalizeWheelID(wheelID)]; ok {
return count
}
return wheelTierCounts["classic"]
}
func defaultProbabilityPercent(index int, count int) float64 {
if count <= 0 {
return 0
}
base := math.Floor(10000/float64(count)) / 100
if index == count-1 {
return 100 - base*float64(count-1)
}
return base
}
func drawFromProto(draw *activityv1.WheelDrawResult) drawDTO {
if draw == nil {
return drawDTO{}
}
return drawDTO{
DrawID: draw.GetDrawId(),
DrawIDs: draw.GetDrawIds(),
CommandID: draw.GetCommandId(),
WheelID: draw.GetWheelId(),
RuleVersion: draw.GetRuleVersion(),
SelectedTierID: draw.GetSelectedTierId(),
RewardType: draw.GetRewardType(),
RewardID: draw.GetRewardId(),
RewardCount: draw.GetRewardCount(),
RewardCoins: draw.GetRewardCoins(),
RTPValueCoins: draw.GetRtpValueCoins(),
RewardStatus: draw.GetRewardStatus(),
RTPWindowIndex: draw.GetRtpWindowIndex(),
ActualRTPPPM: draw.GetActualRtpPpm(),
CreatedAtMS: draw.GetCreatedAtMs(),
WalletTransactionID: draw.GetWalletTransactionId(),
CoinBalanceAfter: draw.GetCoinBalanceAfter(),
MetadataJSON: draw.GetMetadataJson(),
}
}
func drawSummaryFromProto(summary *activityv1.WheelDrawSummary) drawSummaryDTO {
if summary == nil {
return drawSummaryDTO{}
}
return drawSummaryDTO{
WheelID: summary.GetWheelId(),
TotalDraws: summary.GetTotalDraws(),
UniqueUsers: summary.GetUniqueUsers(),
TotalSpentCoins: summary.GetTotalSpentCoins(),
TotalRTPValueCoins: summary.GetTotalRtpValueCoins(),
ActualRTPPPM: summary.GetActualRtpPpm(),
PendingDraws: summary.GetPendingDraws(),
GrantedDraws: summary.GetGrantedDraws(),
FailedDraws: summary.GetFailedDraws(),
}
}
func normalizeRewardType(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "coin":
return "coin"
case "gift":
return "gift"
case "prop", "item", "decoration", "resource":
return "prop"
case "dress", "dress_up", "costume":
return "dress"
default:
return strings.ToLower(strings.TrimSpace(value))
}
}
func normalizeWheelID(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return defaultWheelID
}
return value
}
func generatedTierID(raw string, rewardType string, index int) string {
raw = strings.TrimSpace(raw)
if raw != "" {
return raw
}
// 同一奖池现在允许多个金币、多个礼物或多个装扮;缺省 ID 必须带序号,
// 否则多档同类型会落到同一个 tier_id抽奖结果无法稳定回显到正确格子。
return fmt.Sprintf("%s-%02d", rewardType, index+1)
}
func normalizeMetadataJSON(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return "{}"
}
return value
}
func percentToPPM(value float64) int64 {
return int64(math.Round(value * 10_000))
}
func ppmToPercent(value int64) float64 {
return float64(value) / 10_000
}
func firstQuery(c *gin.Context, names ...string) string {
for _, name := range names {
if value := strings.TrimSpace(c.Query(name)); value != "" {
return value
}
}
return ""
}
func parseOptionalInt64(value string) int64 {
value = strings.TrimSpace(value)
if value == "" {
return 0
}
var result int64
for _, char := range value {
if char < '0' || char > '9' {
return 0
}
result = result*10 + int64(char-'0')
}
return result
}

View File

@ -0,0 +1,114 @@
package wheel
import (
"strings"
"testing"
activityv1 "hyapp.local/api/proto/activity/v1"
)
func TestConfigToProtoSupportsPoolTierCountAndForcesPropRTPZero(t *testing.T) {
tiers := validClassicTiers()
tiers[0] = tierDTO{DisplayName: "金币", RewardType: " coin ", RewardCount: 1, RewardCoins: 1000, ProbabilityPercent: defaultProbabilityPercent(0, 9), Enabled: true}
tiers[1] = tierDTO{DisplayName: "礼物", RewardType: "gift", RewardID: "gift_1", RewardCount: 1, RTPValueCoins: 5000, ProbabilityPercent: defaultProbabilityPercent(1, 9), Enabled: true}
tiers[2] = tierDTO{DisplayName: "装扮", RewardType: "dress_up", RewardID: "dress_1", RewardCount: 1, RTPValueCoins: 999999, ProbabilityPercent: defaultProbabilityPercent(2, 9), Enabled: true}
req := configRequest{
WheelID: " classic ",
Enabled: true,
DrawPriceCoins: 10000,
TargetRTPPercent: 90,
PoolRatePercent: 92,
SettlementWindowDraws: 1000,
Tiers: tiers,
}
if err := validateConfigRequest(req); err != nil {
t.Fatalf("validate config failed: %v", err)
}
config := configToProto(req)
if config.GetWheelId() != "classic" {
t.Fatalf("wheel id = %q, want classic", config.GetWheelId())
}
if config.GetTargetRtpPpm() != 900000 || config.GetPoolRatePpm() != 920000 {
t.Fatalf("ppm mismatch: target=%d pool=%d", config.GetTargetRtpPpm(), config.GetPoolRatePpm())
}
protoTiers := config.GetTiers()
if len(protoTiers) != 9 {
t.Fatalf("tier count = %d, want 9", len(protoTiers))
}
if protoTiers[0].GetTierId() != "coin-01" || protoTiers[0].GetRtpValueCoins() != 1000 {
t.Fatalf("coin tier mismatch: %+v", protoTiers[0])
}
if protoTiers[2].GetRewardType() != "dress" || protoTiers[2].GetRtpValueCoins() != 0 {
t.Fatalf("dress tier must force rtp to zero: %+v", protoTiers[2])
}
}
func TestValidateConfigRequiresPoolTierCountAndProbabilitySum(t *testing.T) {
err := validateConfigRequest(configRequest{
WheelID: "classic",
Tiers: []tierDTO{
{RewardType: "coin", RewardCoins: 100, ProbabilityPercent: 50},
{RewardType: "gift", RewardID: "gift_1", ProbabilityPercent: 30},
{RewardType: "gift", RewardID: "gift_2", ProbabilityPercent: 20},
},
})
if err == nil || !strings.Contains(err.Error(), "9 个奖品档位") {
t.Fatalf("classic tier count should be rejected, got %v", err)
}
tiers := validClassicTiers()
tiers[8].ProbabilityPercent = 1
err = validateConfigRequest(configRequest{
WheelID: "classic",
Tiers: tiers,
})
if err == nil || !strings.Contains(err.Error(), "概率合计") {
t.Fatalf("probability sum should be rejected, got %v", err)
}
}
func TestDefaultConfigDoesNotCreateMockPrizeTiers(t *testing.T) {
config := defaultConfigDTO("classic")
if len(config.Tiers) != 0 {
t.Fatalf("default config must not create mock prize tiers: %+v", config.Tiers)
}
if config.WheelID != "classic" || config.DrawPriceCoins <= 0 {
t.Fatalf("default base config mismatch: %+v", config)
}
}
func TestConfigFromProtoConvertsPPMToPercent(t *testing.T) {
config := configFromProto(&activityv1.WheelRuleConfig{
AppCode: "lalu",
WheelId: "default",
RuleVersion: 3,
Enabled: true,
DrawPriceCoins: 10000,
TargetRtpPpm: 900000,
PoolRatePpm: 920000,
SettlementWindowDraws: 1000,
Tiers: []*activityv1.WheelPrizeTier{
{TierId: "coin", DisplayName: "金币", RewardType: "coin", RewardCoins: 1000, RtpValueCoins: 1000, WeightPpm: 600000, Enabled: true},
},
})
if config.TargetRTPPercent != 90 || config.PoolRatePercent != 92 {
t.Fatalf("percent mismatch: target=%v pool=%v", config.TargetRTPPercent, config.PoolRatePercent)
}
if len(config.Tiers) != 1 || config.Tiers[0].ProbabilityPercent != 60 {
t.Fatalf("tier probability mismatch: %+v", config.Tiers)
}
}
func validClassicTiers() []tierDTO {
tiers := make([]tierDTO, 0, 9)
for index := 0; index < 9; index++ {
tiers = append(tiers, tierDTO{
DisplayName: "金币",
RewardType: "coin",
RewardCount: 1,
RewardCoins: 100,
ProbabilityPercent: defaultProbabilityPercent(index, 9),
Enabled: true,
})
}
return tiers
}

View File

@ -0,0 +1,17 @@
package wheel
import (
"hyapp-admin-server/internal/middleware"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
if h == nil {
return
}
protected.GET("/admin/activity/wheel/config", middleware.RequirePermission("wheel:view"), h.GetConfig)
protected.PUT("/admin/activity/wheel/config", middleware.RequirePermission("wheel:update"), h.UpsertConfig)
protected.GET("/admin/activity/wheel/draws", middleware.RequirePermission("wheel:view"), h.ListDraws)
protected.GET("/admin/activity/wheel/draw-summary", middleware.RequirePermission("wheel:view"), h.GetDrawSummary)
}

View File

@ -36,6 +36,8 @@ var defaultPermissions = []model.Permission{
{Name: "房间置顶取消", Code: "room-pin:cancel", Kind: "button"},
{Name: "房间配置查看", Code: "room-config:view", Kind: "menu"},
{Name: "房间配置更新", Code: "room-config:update", Kind: "button"},
{Name: "房间白名单查看", Code: "room-whitelist:view", Kind: "menu"},
{Name: "房间白名单更新", Code: "room-whitelist:update", Kind: "button"},
{Name: "机器人房间查看", Code: "room-robot:view", Kind: "menu"},
{Name: "机器人房间创建", Code: "room-robot:create", Kind: "button"},
{Name: "机器人房间更新", Code: "room-robot:update", Kind: "button"},
@ -128,6 +130,8 @@ var defaultPermissions = []model.Permission{
{Name: "七日签到更新", Code: "seven-day-checkin:update", Kind: "button"},
{Name: "幸运礼物查看", Code: "lucky-gift:view", Kind: "menu"},
{Name: "幸运礼物更新", Code: "lucky-gift:update", Kind: "button"},
{Name: "转盘抽奖查看", Code: "wheel:view", Kind: "menu"},
{Name: "转盘抽奖更新", Code: "wheel:update", Kind: "button"},
{Name: "房间火箭查看", Code: "room-rocket:view", Kind: "menu"},
{Name: "房间火箭更新", Code: "room-rocket:update", Kind: "button"},
{Name: "用户榜单查看", Code: "user-leaderboard:view", Kind: "menu"},
@ -263,7 +267,8 @@ func (s *Store) seedMenus() error {
{ParentID: &roomsID, Title: "房间列表", Code: "room-list", Path: "/rooms", Icon: "room", PermissionCode: "room:view", Sort: 65, Visible: true},
{ParentID: &roomsID, Title: "房间置顶", Code: "room-pins", Path: "/rooms/pins", Icon: "push_pin", PermissionCode: "room-pin:view", Sort: 66, Visible: true},
{ParentID: &roomsID, Title: "房间配置", Code: "room-config", Path: "/rooms/config", Icon: "settings", PermissionCode: "room-config:view", Sort: 67, Visible: true},
{ParentID: &roomsID, Title: "机器人房间", Code: "room-robots", Path: "/rooms/robots", Icon: "team", PermissionCode: "room-robot:view", Sort: 68, Visible: true},
{ParentID: &roomsID, Title: "房间白名单", Code: "room-whitelist", Path: "/rooms/whitelist", Icon: "shield", PermissionCode: "room-whitelist:view", Sort: 68, Visible: true},
{ParentID: &roomsID, Title: "机器人房间", Code: "room-robots", Path: "/rooms/robots", Icon: "team", PermissionCode: "room-robot:view", Sort: 69, Visible: true},
{ParentID: &appConfigID, Title: "H5配置", Code: "app-config-h5", Path: "/app-config/h5", Icon: "settings", PermissionCode: "app-config:view", Sort: 66, Visible: true},
{ParentID: &appConfigID, Title: "BANNER配置", Code: "app-config-banners", Path: "/app-config/banners", Icon: "image", PermissionCode: "app-config:view", Sort: 67, Visible: true},
{ParentID: &appConfigID, Title: "开屏配置", Code: "app-config-splash-screens", Path: "/app-config/splash-screens", Icon: "image", PermissionCode: "app-config:view", Sort: 68, Visible: true},
@ -280,9 +285,9 @@ func (s *Store) seedMenus() error {
{ParentID: &operationsID, Title: "币商流水", Code: "operation-coin-seller-ledger", Path: "/operations/coin-seller-ledger", Icon: "receipt", PermissionCode: "coin-seller-ledger:view", Sort: 69, Visible: true},
{ParentID: &operationsID, Title: "金币增减", Code: "operation-coin-adjustment", Path: "/operations/coin-adjustments", Icon: "wallet", PermissionCode: "coin-adjustment:view", Sort: 70, Visible: true},
{ParentID: &operationsID, Title: "幸运礼物", Code: "lucky-gift", Path: "/operations/lucky-gift", Icon: "redeem", PermissionCode: "lucky-gift:view", Sort: 71, Visible: true},
{ParentID: &operationsID, Title: "举报列表", Code: "operation-reports", Path: "/operations/reports", Icon: "flag", PermissionCode: "report:view", Sort: 72, Visible: true},
{ParentID: &operationsID, Title: "礼物钻石", Code: "operation-gift-diamond", Path: "/operations/gift-diamonds", Icon: "diamond", PermissionCode: "gift-diamond:view", Sort: 73, Visible: true},
{ParentID: &operationsID, Title: "全服通知", Code: "operation-full-server-notice", Path: "/operations/full-server-notices", Icon: "campaign", PermissionCode: "full-server-notice:view", Sort: 74, Visible: true},
{ParentID: &operationsID, Title: "举报列表", Code: "operation-reports", Path: "/operations/reports", Icon: "flag", PermissionCode: "report:view", Sort: 73, Visible: true},
{ParentID: &operationsID, Title: "礼物钻石", Code: "operation-gift-diamond", Path: "/operations/gift-diamonds", Icon: "diamond", PermissionCode: "gift-diamond:view", Sort: 74, Visible: true},
{ParentID: &operationsID, Title: "全服通知", Code: "operation-full-server-notice", Path: "/operations/full-server-notices", Icon: "campaign", PermissionCode: "full-server-notice:view", Sort: 75, Visible: true},
{ParentID: &paymentID, Title: "账单列表", Code: "payment-bill-list", Path: "/payment/bills", Icon: "receipt", PermissionCode: "payment-bill:view", Sort: 68, Visible: true},
{ParentID: &paymentID, Title: "三方支付", Code: "payment-third-party", Path: "/payment/third-party", Icon: "wallet", PermissionCode: "payment-third-party:view", Sort: 69, Visible: true},
{ParentID: &paymentID, Title: "支付内购商品", Code: "payment-recharge-products", Path: "/payment/recharge-products", Icon: "wallet", PermissionCode: "payment-product:view", Sort: 70, Visible: true},
@ -297,6 +302,7 @@ func (s *Store) seedMenus() error {
{ParentID: &activityID, Title: "VIP配置", Code: "vip-config", Path: "/activities/vip-config", Icon: "workspace_premium", PermissionCode: "vip-config:view", Sort: 77, Visible: true},
{ParentID: &activityID, Title: "周星配置", Code: "weekly-star", Path: "/activities/weekly-star", Icon: "star", PermissionCode: "weekly-star:view", Sort: 78, Visible: true},
{ParentID: &activityID, Title: "代理开业活动", Code: "agency-opening", Path: "/activities/agency-opening", Icon: "workspace_premium", PermissionCode: "agency-opening:view", Sort: 79, Visible: true},
{ParentID: &activityID, Title: "转盘抽奖", Code: "wheel", Path: "/activities/wheel", Icon: "casino", PermissionCode: "wheel:view", Sort: 80, Visible: true},
{ParentID: &gameID, Title: "游戏列表", Code: "game-list", Path: "/games", Icon: "sports_esports", PermissionCode: "game:view", Sort: 70, Visible: true},
{ParentID: &gameID, Title: "自研游戏", Code: "self-games", Path: "/games/self-games", Icon: "settings", PermissionCode: "game:view", Sort: 80, Visible: true},
{ParentID: &gameID, Title: "全站机器人", Code: "game-robots", Path: "/games/robots", Icon: "team", PermissionCode: "game:view", Sort: 90, Visible: true},
@ -522,7 +528,7 @@ func defaultRolePermissionCodes(code string) []string {
"level-config:view", "level-config:update",
"pretty-id:view", "pretty-id:update", "pretty-id:generate", "pretty-id:grant",
"region-block:view", "region-block:update",
"room:view", "room:update", "room:delete", "room-pin:view", "room-pin:create", "room-pin:cancel", "room-config:view", "room-config:update", "room-robot:view", "room-robot:create", "room-robot:update",
"room:view", "room:update", "room:delete", "room-pin:view", "room-pin:create", "room-pin:cancel", "room-config:view", "room-config:update", "room-whitelist:view", "room-whitelist:update", "room-robot:view", "room-robot:create", "room-robot:update",
"app-config:view", "app-config:update",
"app-version:view", "app-version:create", "app-version:update", "app-version:delete",
"resource:view", "resource:create", "resource:update",
@ -538,7 +544,7 @@ func defaultRolePermissionCodes(code string) []string {
"bd:view", "bd:create", "bd:update",
"coin-seller:view", "coin-seller:create", "coin-seller:update", "coin-seller:stock-credit", "coin-seller:exchange-rate",
"coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "coin-adjustment:create", "report:view", "gift-diamond:view", "gift-diamond:update", "full-server-notice:view", "full-server-notice:send", "payment-bill:view", "payment-third-party:view", "payment-third-party:update", "payment-product:view", "payment-product:create", "payment-product:update", "payment-product:delete",
"lucky-gift:view", "lucky-gift:update",
"lucky-gift:view", "lucky-gift:update", "wheel:view", "wheel:update",
"game:view", "game:create", "game:update", "game:status", "game:delete",
"daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status",
"achievement:view", "achievement:create", "achievement:update",
@ -554,7 +560,7 @@ func defaultRolePermissionCodes(code string) []string {
"upload:create",
}
case "auditor":
return []string{"overview:view", "log:view", "user:view", "app-user:view", "level-config:view", "pretty-id:view", "region-block:view", "room:view", "room-pin:view", "room-config:view", "room-robot:view", "app-config:view", "app-version:view", "resource:view", "resource-shop:view", "resource-group:view", "resource-grant:view", "gift:view", "emoji-pack:view", "host-agency-policy:view", "team-salary-policy:view", "host-salary-settlement:view", "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "report:view", "gift-diamond:view", "full-server-notice:view", "lucky-gift:view", "payment-bill:view", "payment-third-party:view", "payment-product:view", "game:view", "daily-task:view", "achievement:view", "seven-day-checkin:view", "room-rocket:view", "red-packet:view", "cp-config:view", "vip-config:view", "weekly-star:view", "agency-opening:view", "role:view", "permission:view", "job:view"}
return []string{"overview:view", "log:view", "user:view", "app-user:view", "level-config:view", "pretty-id:view", "region-block:view", "room:view", "room-pin:view", "room-config:view", "room-whitelist:view", "room-robot:view", "app-config:view", "app-version:view", "resource:view", "resource-shop:view", "resource-group:view", "resource-grant:view", "gift:view", "emoji-pack:view", "host-agency-policy:view", "team-salary-policy:view", "host-salary-settlement:view", "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "report:view", "gift-diamond:view", "full-server-notice:view", "lucky-gift:view", "wheel:view", "payment-bill:view", "payment-third-party:view", "payment-product:view", "game:view", "daily-task:view", "achievement:view", "seven-day-checkin:view", "room-rocket:view", "red-packet:view", "cp-config:view", "vip-config:view", "weekly-star:view", "agency-opening:view", "role:view", "permission:view", "job:view"}
case "readonly":
return []string{
"overview:view",
@ -566,6 +572,7 @@ func defaultRolePermissionCodes(code string) []string {
"room:view",
"room-pin:view",
"room-config:view",
"room-whitelist:view",
"room-robot:view",
"app-config:view",
"app-version:view",
@ -591,6 +598,7 @@ func defaultRolePermissionCodes(code string) []string {
"gift-diamond:view",
"full-server-notice:view",
"lucky-gift:view",
"wheel:view",
"payment-bill:view",
"payment-third-party:view",
"payment-product:view",
@ -620,7 +628,7 @@ func defaultRolePermissionMigrationCodes(code string) []string {
case "ops-admin":
return []string{
"app-user:update", "app-user:status", "app-user:password",
"room:view", "room:update", "room:delete", "room-pin:view", "room-pin:create", "room-pin:cancel", "room-config:view", "room-config:update", "room-robot:view", "room-robot:create", "room-robot:update",
"room:view", "room:update", "room:delete", "room-pin:view", "room-pin:create", "room-pin:cancel", "room-config:view", "room-config:update", "room-whitelist:view", "room-whitelist:update", "room-robot:view", "room-robot:create", "room-robot:update",
"app-config:view", "app-config:update",
"app-version:view", "app-version:create", "app-version:update", "app-version:delete",
"level-config:view", "level-config:update",
@ -638,7 +646,7 @@ func defaultRolePermissionMigrationCodes(code string) []string {
"host-agency-policy:view", "host-agency-policy:create", "host-agency-policy:update", "host-agency-policy:delete", "host-agency-policy:publish", "team-salary-policy:view", "team-salary-policy:create", "team-salary-policy:update", "team-salary-policy:delete", "host-salary-settlement:view", "host-salary-settlement:settle",
"coin-seller:view", "coin-seller:create", "coin-seller:update", "coin-seller:stock-credit", "coin-seller:exchange-rate",
"coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "coin-adjustment:create", "report:view", "gift-diamond:view", "gift-diamond:update", "full-server-notice:view", "full-server-notice:send", "payment-bill:view", "payment-third-party:view", "payment-third-party:update", "payment-product:view", "payment-product:create", "payment-product:update", "payment-product:delete",
"lucky-gift:view", "lucky-gift:update",
"lucky-gift:view", "lucky-gift:update", "wheel:view", "wheel:update",
"game:view", "game:create", "game:update", "game:status", "game:delete",
"daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status",
"achievement:view", "achievement:create", "achievement:update",
@ -651,7 +659,7 @@ func defaultRolePermissionMigrationCodes(code string) []string {
"agency-opening:view", "agency-opening:create", "agency-opening:update",
}
case "auditor", "readonly":
return []string{"level-config:view", "pretty-id:view", "region-block:view", "room:view", "room-pin:view", "room-config:view", "room-robot:view", "app-config:view", "app-version:view", "resource:view", "resource-shop:view", "resource-group:view", "resource-grant:view", "gift:view", "emoji-pack:view", "host-agency-policy:view", "team-salary-policy:view", "host-salary-settlement:view", "coin-seller:view", "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "report:view", "gift-diamond:view", "full-server-notice:view", "lucky-gift:view", "payment-bill:view", "payment-third-party:view", "payment-product:view", "game:view", "daily-task:view", "achievement:view", "seven-day-checkin:view", "room-rocket:view", "red-packet:view", "cp-config:view", "vip-config:view", "weekly-star:view", "agency-opening:view"}
return []string{"level-config:view", "pretty-id:view", "region-block:view", "room:view", "room-pin:view", "room-config:view", "room-whitelist:view", "room-robot:view", "app-config:view", "app-version:view", "resource:view", "resource-shop:view", "resource-group:view", "resource-grant:view", "gift:view", "emoji-pack:view", "host-agency-policy:view", "team-salary-policy:view", "host-salary-settlement:view", "coin-seller:view", "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "report:view", "gift-diamond:view", "full-server-notice:view", "lucky-gift:view", "wheel:view", "payment-bill:view", "payment-third-party:view", "payment-product:view", "game:view", "daily-task:view", "achievement:view", "seven-day-checkin:view", "room-rocket:view", "red-packet:view", "cp-config:view", "vip-config:view", "weekly-star:view", "agency-opening:view"}
default:
return nil
}

View File

@ -49,6 +49,7 @@ import (
"hyapp-admin-server/internal/modules/userleaderboard"
"hyapp-admin-server/internal/modules/vipconfig"
"hyapp-admin-server/internal/modules/weeklystar"
"hyapp-admin-server/internal/modules/wheel"
"hyapp-admin-server/internal/platform/logging"
"hyapp-admin-server/internal/service"
@ -102,6 +103,7 @@ type Handlers struct {
UserLeaderboard *userleaderboard.Handler
VIPConfig *vipconfig.Handler
WeeklyStar *weeklystar.Handler
Wheel *wheel.Handler
}
func New(cfg config.Config, auth *service.AuthService, h Handlers) *gin.Engine {
@ -160,6 +162,7 @@ func New(cfg config.Config, auth *service.AuthService, h Handlers) *gin.Engine {
userleaderboard.RegisterRoutes(protected, h.UserLeaderboard)
vipconfig.RegisterRoutes(protected, h.VIPConfig)
weeklystar.RegisterRoutes(protected, h.WeeklyStar)
wheel.RegisterRoutes(protected, h.Wheel)
return engine
}

View File

@ -5,7 +5,7 @@ SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE admin_app_banners
ADD COLUMN room_small_image_url VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '房间内小屏图 URL' AFTER cover_url,
ADD COLUMN display_scope VARCHAR(32) NOT NULL DEFAULT 'home' COMMENT '显示范围:首页、房间内或充值' AFTER banner_type,
ADD COLUMN display_scope VARCHAR(32) NOT NULL DEFAULT 'home' COMMENT '显示范围:首页、房间内、充值页或我的' AFTER banner_type,
ADD COLUMN starts_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '投放开始时间UTC epoch ms' AFTER status,
ADD COLUMN ends_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '投放结束时间UTC epoch ms' AFTER starts_at_ms,
ADD INDEX idx_admin_app_banners_display (app_code, display_scope, status, starts_at_ms, ends_at_ms, sort_order, id);

View File

@ -1,6 +1,6 @@
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Banner 显示范围支持多选display_scope 以稳定逗号分隔值保存,例如 home,room,recharge
-- Banner 显示范围支持多选display_scope 以稳定逗号分隔值保存,例如 home,room,recharge,me
ALTER TABLE admin_app_banners
MODIFY COLUMN display_scope VARCHAR(128) NOT NULL DEFAULT 'home' COMMENT '显示范围,多选逗号分隔:首页、房间内或充值';
MODIFY COLUMN display_scope VARCHAR(128) NOT NULL DEFAULT 'home' COMMENT '显示范围,多选逗号分隔:首页、房间内、充值页或我的';

View File

@ -0,0 +1,41 @@
-- 房间白名单允许指定用户在 App 房间列表绕过 visible_region_id 过滤;本迁移只写后台菜单和权限事实。
SET @now_ms := UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000;
INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES
('房间白名单查看', 'room-whitelist:view', 'menu', '允许查看房间白名单配置', @now_ms, @now_ms),
('房间白名单更新', 'room-whitelist:update', 'button', '允许更新房间白名单配置', @now_ms, @now_ms)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
kind = VALUES(kind),
description = VALUES(description),
updated_at_ms = VALUES(updated_at_ms);
INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms)
SELECT parent.id, '房间白名单', 'room-whitelist', '/rooms/whitelist', 'shield', 'room-whitelist:view', 68, TRUE, @now_ms, @now_ms
FROM admin_menus parent
WHERE parent.code = 'rooms'
ON DUPLICATE KEY UPDATE
parent_id = VALUES(parent_id),
title = VALUES(title),
path = VALUES(path),
icon = VALUES(icon),
permission_code = VALUES(permission_code),
sort = VALUES(sort),
visible = VALUES(visible),
updated_at_ms = VALUES(updated_at_ms);
UPDATE admin_menus
SET sort = 69, updated_at_ms = @now_ms
WHERE code = 'room-robots' AND sort < 69;
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM admin_roles r
JOIN admin_permissions p ON p.code IN ('room-whitelist:view', 'room-whitelist:update')
WHERE r.code IN ('platform-admin', 'ops-admin');
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM admin_roles r
JOIN admin_permissions p ON p.code = 'room-whitelist:view'
WHERE r.code IN ('auditor', 'readonly');

View File

@ -0,0 +1,59 @@
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 转盘抽奖规则、记录和统计都归 activity-service后台提供活动管理入口和 gRPC 配置适配。
SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);
INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES
('转盘抽奖查看', 'wheel:view', 'menu', '', @now_ms, @now_ms),
('转盘抽奖更新', 'wheel:update', 'button', '', @now_ms, @now_ms)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
kind = VALUES(kind),
description = VALUES(description),
updated_at_ms = @now_ms;
INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms) VALUES
(NULL, '活动管理', 'activities', '', 'campaign', '', 80, TRUE, @now_ms, @now_ms)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
path = VALUES(path),
icon = VALUES(icon),
permission_code = VALUES(permission_code),
sort = VALUES(sort),
visible = VALUES(visible),
updated_at_ms = @now_ms;
INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms)
SELECT parent.id, '转盘抽奖', 'wheel', '/activities/wheel', 'casino', 'wheel:view', 80, TRUE, @now_ms, @now_ms
FROM admin_menus parent
WHERE parent.code = 'activities'
ON DUPLICATE KEY UPDATE
parent_id = VALUES(parent_id),
title = VALUES(title),
path = VALUES(path),
icon = VALUES(icon),
permission_code = VALUES(permission_code),
sort = VALUES(sort),
visible = VALUES(visible),
updated_at_ms = @now_ms;
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT admin_role.id, admin_permission.id
FROM admin_roles admin_role
JOIN admin_permissions admin_permission
WHERE admin_role.code = 'platform-admin'
AND admin_permission.code IN ('wheel:view', 'wheel:update');
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT admin_role.id, admin_permission.id
FROM admin_roles admin_role
JOIN admin_permissions admin_permission
WHERE admin_role.code = 'ops-admin'
AND admin_permission.code IN ('wheel:view', 'wheel:update');
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT admin_role.id, admin_permission.id
FROM admin_roles admin_role
JOIN admin_permissions admin_permission
WHERE admin_role.code IN ('auditor', 'readonly')
AND admin_permission.code = 'wheel:view';

View File

@ -0,0 +1,26 @@
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 056 初版曾把转盘抽奖放在运营管理;这里单独迁移到活动管理,保证已执行过 056 的库也能修正菜单位置。
SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);
INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms) VALUES
(NULL, '活动管理', 'activities', '', 'campaign', '', 80, TRUE, @now_ms, @now_ms)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
path = VALUES(path),
icon = VALUES(icon),
permission_code = VALUES(permission_code),
sort = VALUES(sort),
visible = VALUES(visible),
updated_at_ms = @now_ms;
UPDATE admin_menus wheel
JOIN admin_menus activities ON activities.code = 'activities'
SET wheel.parent_id = activities.id,
wheel.path = '/activities/wheel',
wheel.sort = 80,
wheel.icon = 'casino',
wheel.permission_code = 'wheel:view',
wheel.visible = TRUE,
wheel.updated_at_ms = @now_ms
WHERE wheel.code = 'wheel';

View File

@ -37,9 +37,9 @@ lucky_gift_worker:
tencent_im:
# Docker 本地联调全局/区域播报群;必须与 gateway/room-service 使用同一组腾讯 IM 应用配置。
enabled: true
sdk_app_id: 20036101
secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c"
admin_identifier: "900100100"
sdk_app_id: 20040101
secret_key: "2f6a5ec58cccf79647557a96c15e6efe89ba5a44c474096bde2589a633fcd45e"
admin_identifier: "administrator"
admin_user_sig_ttl: "24h"
endpoint: "adminapisgp.im.qcloud.com"
group_type: "ChatRoom"

View File

@ -37,9 +37,9 @@ lucky_gift_worker:
tencent_im:
# activity-service 只负责全局/区域播报群;必须与 gateway/room-service 使用同一组腾讯 IM 应用配置。
enabled: true
sdk_app_id: 20036101
secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c"
admin_identifier: "900100100"
sdk_app_id: 20040101
secret_key: "2f6a5ec58cccf79647557a96c15e6efe89ba5a44c474096bde2589a633fcd45e"
admin_identifier: "administrator"
admin_user_sig_ttl: "24h"
endpoint: "adminapisgp.im.qcloud.com"
group_type: "ChatRoom"

View File

@ -287,6 +287,131 @@ CREATE TABLE IF NOT EXISTS lucky_draw_pool_stat_cursors (
PRIMARY KEY (app_code, cursor_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物后台统计快照游标';
CREATE TABLE IF NOT EXISTS wheel_rule_versions (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
rule_version BIGINT NOT NULL COMMENT '不可变规则版本',
enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用',
draw_price_coins BIGINT NOT NULL COMMENT '单抽价格,金币',
target_rtp_ppm BIGINT NOT NULL COMMENT '金币和礼物 RTP 目标ppm',
pool_rate_ppm BIGINT NOT NULL COMMENT '每笔消耗进入转盘基础奖池比例ppm',
settlement_window_draws BIGINT NOT NULL COMMENT 'RTP 观察窗口抽数',
initial_pool_coins BIGINT NOT NULL DEFAULT 0 COMMENT '初始奖池水位',
pool_reserve_coins BIGINT NOT NULL DEFAULT 0 COMMENT '奖池安全水位',
max_single_rtp_payout BIGINT NOT NULL DEFAULT 0 COMMENT '单次计入 RTP 的最大返奖',
effective_from_ms BIGINT NOT NULL COMMENT '生效时间UTC epoch ms',
created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '发布人',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id, rule_version),
KEY idx_wheel_rule_versions_latest (app_code, wheel_id, rule_version),
KEY idx_wheel_rule_versions_enabled (app_code, enabled, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘规则版本表';
CREATE TABLE IF NOT EXISTS wheel_prize_tiers (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
rule_version BIGINT NOT NULL COMMENT '规则版本',
tier_id VARCHAR(96) NOT NULL COMMENT '奖档 ID',
display_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '展示名称快照',
reward_type VARCHAR(32) NOT NULL COMMENT 'coin/gift/prop/dress',
reward_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '礼物或道具资源 ID',
reward_count BIGINT NOT NULL DEFAULT 1 COMMENT '奖励数量',
reward_coins BIGINT NOT NULL DEFAULT 0 COMMENT '金币奖励金额',
rtp_value_coins BIGINT NOT NULL DEFAULT 0 COMMENT '计入 RTP 的奖励价值;道具装扮强制为 0',
weight_ppm BIGINT NOT NULL COMMENT '随机权重ppm 口径',
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
metadata_json JSON NOT NULL COMMENT '奖品展示和发放扩展快照',
PRIMARY KEY (app_code, wheel_id, rule_version, tier_id),
KEY idx_wheel_prize_tiers_rule (app_code, wheel_id, rule_version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘奖档表';
CREATE TABLE IF NOT EXISTS wheel_rtp_windows (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
window_index BIGINT NOT NULL COMMENT 'RTP 窗口序号',
target_rtp_ppm BIGINT NOT NULL COMMENT '窗口目标 RTP',
control_window_draws BIGINT NOT NULL COMMENT '窗口抽数',
paid_draws BIGINT NOT NULL DEFAULT 0 COMMENT '已抽次数',
wager_coins BIGINT NOT NULL DEFAULT 0 COMMENT '消耗金币',
target_payout_coins BIGINT NOT NULL DEFAULT 0 COMMENT '目标返奖',
actual_rtp_value_coins BIGINT NOT NULL DEFAULT 0 COMMENT '实际计入 RTP 的返奖',
carry_ppm BIGINT NOT NULL DEFAULT 0 COMMENT '整数除法余数',
status VARCHAR(32) NOT NULL DEFAULT 'open' COMMENT 'open/closed',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id, window_index),
KEY idx_wheel_rtp_windows_open (app_code, wheel_id, status, window_index)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘 RTP 观察窗口';
CREATE TABLE IF NOT EXISTS wheel_pools (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
balance BIGINT NOT NULL DEFAULT 0 COMMENT '当前可用水位',
reserve_floor BIGINT NOT NULL DEFAULT 0 COMMENT '安全水位',
total_in BIGINT NOT NULL DEFAULT 0 COMMENT '累计入池',
total_out BIGINT NOT NULL DEFAULT 0 COMMENT '累计 RTP 价值支出',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘基础奖池';
CREATE TABLE IF NOT EXISTS wheel_draw_records (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
draw_id VARCHAR(128) NOT NULL COMMENT '抽奖 ID',
command_id VARCHAR(128) NOT NULL COMMENT '幂等命令 ID',
user_id BIGINT NOT NULL COMMENT '用户 ID',
device_id VARCHAR(128) NOT NULL COMMENT '设备 ID',
visible_region_id BIGINT NOT NULL DEFAULT 0 COMMENT '可见区域 ID',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
coin_spent BIGINT NOT NULL COMMENT '消耗金币',
rule_version BIGINT NOT NULL COMMENT '规则版本',
rtp_window_index BIGINT NOT NULL COMMENT 'RTP 窗口',
selected_tier_id VARCHAR(96) NOT NULL COMMENT '命中奖档',
reward_type VARCHAR(32) NOT NULL COMMENT 'coin/gift/prop/dress',
reward_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '礼物或道具资源 ID',
reward_count BIGINT NOT NULL DEFAULT 0 COMMENT '奖励数量',
reward_coins BIGINT NOT NULL DEFAULT 0 COMMENT '金币奖励金额',
rtp_value_coins BIGINT NOT NULL DEFAULT 0 COMMENT '计入 RTP 的奖励价值',
candidate_tiers_json JSON NOT NULL COMMENT '候选过滤快照',
rtp_snapshot_json JSON NOT NULL COMMENT 'RTP 快照',
prize_metadata_json JSON NOT NULL COMMENT '奖品扩展快照',
reward_status VARCHAR(32) NOT NULL COMMENT 'pending/granted/failed',
reward_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包或资产发放交易 ID',
reward_failure_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '发放失败原因',
paid_at_ms BIGINT NOT NULL COMMENT '扣费时间UTC epoch ms',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, draw_id),
UNIQUE KEY uk_wheel_draw_command (app_code, command_id),
KEY idx_wheel_draw_wheel (app_code, wheel_id, created_at_ms),
KEY idx_wheel_draw_user (app_code, user_id, created_at_ms),
KEY idx_wheel_draw_status (app_code, reward_status, updated_at_ms),
KEY idx_wheel_draw_created (app_code, created_at_ms, draw_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘抽奖事实';
CREATE TABLE IF NOT EXISTS wheel_draw_stats (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
total_draws BIGINT NOT NULL DEFAULT 0 COMMENT '抽奖次数',
unique_users BIGINT NOT NULL DEFAULT 0 COMMENT '参与用户数',
total_spent_coins BIGINT NOT NULL DEFAULT 0 COMMENT '消耗金币',
total_rtp_value_coins BIGINT NOT NULL DEFAULT 0 COMMENT '计入 RTP 的返奖价值',
pending_draws BIGINT NOT NULL DEFAULT 0 COMMENT '待发放数量',
granted_draws BIGINT NOT NULL DEFAULT 0 COMMENT '已发放数量',
failed_draws BIGINT NOT NULL DEFAULT 0 COMMENT '发放失败数量',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘后台汇总统计';
CREATE TABLE IF NOT EXISTS wheel_draw_stat_users (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
user_id BIGINT NOT NULL COMMENT '用户 ID',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘统计用户去重';
CREATE TABLE IF NOT EXISTS im_broadcast_outbox (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离',
event_id VARCHAR(128) NOT NULL COMMENT '事件幂等 ID',
@ -1352,12 +1477,13 @@ CREATE TABLE IF NOT EXISTS agency_opening_cycles (
CREATE TABLE IF NOT EXISTS agency_opening_cycle_rewards (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
cycle_id VARCHAR(96) NOT NULL COMMENT '代理开业周期 ID',
rank_no INT NOT NULL COMMENT '榜单名次',
rank_no INT NOT NULL COMMENT '档位序号',
threshold_coin_spent BIGINT NOT NULL DEFAULT 0 COMMENT '命中该档需要的代理房间流水金币',
reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '奖励金币',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, cycle_id, rank_no)
) 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 agency_opening_applications (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
@ -1368,14 +1494,19 @@ CREATE TABLE IF NOT EXISTS agency_opening_applications (
agency_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '代理名称快照',
host_count INT NOT NULL DEFAULT 0 COMMENT '申请时有效主播数快照',
agency_created_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '代理创建时间快照',
status VARCHAR(32) NOT NULL DEFAULT 'applied' COMMENT 'applied/pending/granted/failed',
status VARCHAR(32) NOT NULL DEFAULT 'applied' COMMENT 'applied/approved/pending/granted/failed/expired',
score_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '周期内开业流水金币',
reward_rank_no INT NOT NULL DEFAULT 0 COMMENT '结算榜单名次',
reward_threshold_coin_spent BIGINT NOT NULL DEFAULT 0 COMMENT '结算命中的流水阈值',
reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '结算奖励金币',
wallet_command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包幂等命令',
wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包交易 ID',
failure_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '失败原因',
applied_at_ms BIGINT NOT NULL COMMENT '申请时间',
approved_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '后台同意时间',
score_start_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分开始时间',
score_end_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分截止时间',
reviewed_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '审核管理员',
granted_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '发奖时间',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',

View File

@ -40,7 +40,7 @@ CREATE TABLE IF NOT EXISTS agency_opening_applications (
agency_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '代理名称快照',
host_count INT NOT NULL DEFAULT 0 COMMENT '申请时有效主播数快照',
agency_created_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '代理创建时间快照',
status VARCHAR(32) NOT NULL DEFAULT 'applied' COMMENT 'applied/pending/granted/failed',
status VARCHAR(32) NOT NULL DEFAULT 'applied' COMMENT 'applied/approved/pending/granted/failed/expired',
score_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '周期内开业流水金币',
reward_rank_no INT NOT NULL DEFAULT 0 COMMENT '结算命中的档位序号',
reward_threshold_coin_spent BIGINT NOT NULL DEFAULT 0 COMMENT '结算命中的流水阈值',
@ -49,6 +49,10 @@ CREATE TABLE IF NOT EXISTS agency_opening_applications (
wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包交易 ID',
failure_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '失败原因',
applied_at_ms BIGINT NOT NULL COMMENT '申请时间',
approved_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '后台同意时间',
score_start_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分开始时间',
score_end_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分截止时间',
reviewed_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '审核管理员',
granted_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '发奖时间',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',

View File

@ -20,6 +20,8 @@ func registerGRPCServers(server *grpc.Server, services *serviceBundle) {
activityv1.RegisterAdminAchievementServiceServer(server, grpcserver.NewAdminAchievementServer(services.achievement))
activityv1.RegisterLuckyGiftServiceServer(server, grpcserver.NewLuckyGiftServer(services.luckyGift))
activityv1.RegisterAdminLuckyGiftServiceServer(server, grpcserver.NewAdminLuckyGiftServer(services.luckyGift))
activityv1.RegisterWheelServiceServer(server, grpcserver.NewWheelServer(services.wheel))
activityv1.RegisterAdminWheelServiceServer(server, grpcserver.NewAdminWheelServer(services.wheel))
activityv1.RegisterRegistrationRewardServiceServer(server, grpcserver.NewRegistrationRewardServer(services.registrationReward))
activityv1.RegisterAdminRegistrationRewardServiceServer(server, grpcserver.NewAdminRegistrationRewardServer(services.registrationReward))
activityv1.RegisterSevenDayCheckInServiceServer(server, grpcserver.NewSevenDayCheckInServer(services.sevenDayCheckIn))

View File

@ -24,6 +24,7 @@ import (
sevendaycheckinservice "hyapp/services/activity-service/internal/service/sevendaycheckin"
taskservice "hyapp/services/activity-service/internal/service/task"
weeklystarservice "hyapp/services/activity-service/internal/service/weeklystar"
wheelservice "hyapp/services/activity-service/internal/service/wheel"
mysqlstorage "hyapp/services/activity-service/internal/storage/mysql"
)
@ -41,6 +42,7 @@ type serviceBundle struct {
roomTurnoverReward *roomturnoverrewardservice.Service
broadcast *broadcastservice.Service
luckyGift *luckygiftservice.Service
wheel *wheelservice.Service
firstRechargeReward *firstrechargeservice.Service
cumulativeRecharge *cumulativerechargeservice.Service
inviteActivityReward *inviteactivityservice.Service
@ -96,6 +98,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository,
luckygiftservice.WithRoomPublisher(tencentClient),
luckygiftservice.WithRegionBroadcaster(broadcastSvc),
)
wheelSvc := wheelservice.New(repository, wheelservice.WithWallet(walletClient))
firstRechargeRewardSvc := firstrechargeservice.New(repository, walletClient)
cumulativeRechargeSvc := cumulativerechargeservice.New(repository, walletClient)
inviteActivityRewardSvc := inviteactivityservice.New(repository, walletClient, activityclient.NewGRPCInviteAttributionSource(clients.userConn))
@ -114,6 +117,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository,
roomTurnoverReward: roomTurnoverRewardSvc,
broadcast: broadcastSvc,
luckyGift: luckyGiftSvc,
wheel: wheelSvc,
firstRechargeReward: firstRechargeRewardSvc,
cumulativeRecharge: cumulativeRechargeSvc,
inviteActivityReward: inviteActivityRewardSvc,

View File

@ -9,10 +9,12 @@ const (
StatusSettling = "settling"
StatusSettled = "settled"
ApplicationStatusApplied = "applied"
ApplicationStatusPending = "pending"
ApplicationStatusGranted = "granted"
ApplicationStatusFailed = "failed"
ApplicationStatusApplied = "applied"
ApplicationStatusApproved = "approved"
ApplicationStatusPending = "pending"
ApplicationStatusGranted = "granted"
ApplicationStatusFailed = "failed"
ApplicationStatusExpired = "expired"
EventStatusConsumed = "consumed"
EventStatusDuplicate = "duplicate"
@ -86,6 +88,10 @@ type Application struct {
WalletTransactionID string
FailureReason string
AppliedAtMS int64
ApprovedAtMS int64
ScoreStartMS int64
ScoreEndMS int64
ReviewedByAdminID int64
GrantedAtMS int64
CreatedAtMS int64
UpdatedAtMS int64

View File

@ -0,0 +1,130 @@
package lotteryengine
import (
crand "crypto/rand"
"errors"
"math/big"
"strings"
)
const (
PrizeKindCoin = "coin"
PrizeKindGift = "gift"
PrizeKindProp = "prop"
PrizeKindDress = "dress"
)
var (
ErrNoCandidate = errors.New("lottery candidate is empty")
ErrInvalidWeight = errors.New("lottery candidate weight is invalid")
)
// Candidate 是抽奖引擎唯一关心的奖档视图。
// 业务模块负责在进入引擎前完成资格、奖池和风控过滤;引擎只保证权重随机和 RTP 计值口径一致。
type Candidate struct {
ID string
Weight int64
PrizeKind string
RTPValueCoins int64
}
// NormalizeCandidates 复制并清洗候选集,避免调用方的业务快照被引擎内部兜底权重改写。
// 全部候选权重都为 0 时退化成等权随机,是为了兼容旧配置或脏数据;负权重直接拒绝,防止配置错误改变概率方向。
func NormalizeCandidates(candidates []Candidate) ([]Candidate, error) {
if len(candidates) == 0 {
return nil, ErrNoCandidate
}
out := make([]Candidate, 0, len(candidates))
var totalWeight int64
for _, candidate := range candidates {
if candidate.Weight < 0 {
return nil, ErrInvalidWeight
}
candidate.PrizeKind = NormalizePrizeKind(candidate.PrizeKind)
candidate.RTPValueCoins = NormalizeRTPValueCoins(candidate.PrizeKind, candidate.RTPValueCoins)
if candidate.Weight > 0 {
totalWeight += candidate.Weight
out = append(out, candidate)
}
}
if totalWeight > 0 {
return out, nil
}
out = out[:0]
for _, candidate := range candidates {
candidate.PrizeKind = NormalizePrizeKind(candidate.PrizeKind)
candidate.RTPValueCoins = NormalizeRTPValueCoins(candidate.PrizeKind, candidate.RTPValueCoins)
candidate.Weight = 1
out = append(out, candidate)
}
return out, nil
}
// SelectWeighted 从已经归一化的候选中按权重抽一档。
// 随机源使用 crypto/rand避免高价值奖档被时间种子或并发顺序预测。
func SelectWeighted(candidates []Candidate) (Candidate, error) {
normalized, err := NormalizeCandidates(candidates)
if err != nil {
return Candidate{}, err
}
var totalWeight int64
for _, candidate := range normalized {
totalWeight += candidate.Weight
}
if totalWeight <= 0 {
return Candidate{}, ErrNoCandidate
}
index, err := secureIndex(totalWeight)
if err != nil {
return Candidate{}, err
}
for _, candidate := range normalized {
if index < candidate.Weight {
return candidate, nil
}
index -= candidate.Weight
}
return normalized[len(normalized)-1], nil
}
// NormalizePrizeKind 把后台和活动配置里的同义奖品类型收敛到引擎内部口径。
// prop 和 dress 都表示装扮/道具类权益,后续发放可以不同,但 RTP 口径必须一致归零。
func NormalizePrizeKind(kind string) string {
switch strings.ToLower(strings.TrimSpace(kind)) {
case PrizeKindCoin:
return PrizeKindCoin
case PrizeKindGift:
return PrizeKindGift
case PrizeKindProp, "item", "decoration", "resource":
return PrizeKindProp
case PrizeKindDress, "dress_up", "costume":
return PrizeKindDress
default:
return strings.ToLower(strings.TrimSpace(kind))
}
}
// NormalizeRTPValueCoins 是转盘和其它抽奖共用的 RTP 计值入口。
// 装扮/道具无论后台配置了多少展示价值,都不能进入 RTP 返奖分子;金币和礼物保留配置值,负数按 0 处理。
func NormalizeRTPValueCoins(kind string, value int64) int64 {
if value < 0 {
value = 0
}
switch NormalizePrizeKind(kind) {
case PrizeKindProp, PrizeKindDress:
return 0
default:
return value
}
}
func secureIndex(totalWeight int64) (int64, error) {
if totalWeight <= 0 {
return 0, ErrNoCandidate
}
n, err := crand.Int(crand.Reader, big.NewInt(totalWeight))
if err != nil {
return 0, err
}
return n.Int64(), nil
}

View File

@ -0,0 +1,44 @@
package lotteryengine
import "testing"
func TestNormalizeCandidatesKeepsPositiveWeights(t *testing.T) {
candidates, err := NormalizeCandidates([]Candidate{
{ID: "zero", Weight: 0, PrizeKind: PrizeKindCoin, RTPValueCoins: 10},
{ID: "coin", Weight: 7, PrizeKind: PrizeKindCoin, RTPValueCoins: 20},
})
if err != nil {
t.Fatalf("NormalizeCandidates failed: %v", err)
}
if len(candidates) != 1 || candidates[0].ID != "coin" || candidates[0].Weight != 7 || candidates[0].RTPValueCoins != 20 {
t.Fatalf("unexpected normalized candidates: %#v", candidates)
}
}
func TestNormalizeCandidatesFallsBackToEqualWeight(t *testing.T) {
candidates, err := NormalizeCandidates([]Candidate{
{ID: "a", PrizeKind: PrizeKindCoin},
{ID: "b", PrizeKind: PrizeKindGift},
})
if err != nil {
t.Fatalf("NormalizeCandidates failed: %v", err)
}
if len(candidates) != 2 || candidates[0].Weight != 1 || candidates[1].Weight != 1 {
t.Fatalf("zero-weight candidates should become equal-weight candidates: %#v", candidates)
}
}
func TestNormalizeRTPValueCoinsForcesPropsAndDressToZero(t *testing.T) {
tests := []string{PrizeKindProp, PrizeKindDress, "item", "decoration", "resource", "costume"}
for _, kind := range tests {
if got := NormalizeRTPValueCoins(kind, 999_999); got != 0 {
t.Fatalf("kind %s should not count into RTP, got %d", kind, got)
}
}
if got := NormalizeRTPValueCoins(PrizeKindCoin, 500); got != 500 {
t.Fatalf("coin should keep rtp value, got %d", got)
}
if got := NormalizeRTPValueCoins(PrizeKindGift, -1); got != 0 {
t.Fatalf("negative gift rtp value should clamp to zero, got %d", got)
}
}

View File

@ -0,0 +1,102 @@
package wheel
const (
StatusDraft = "draft"
StatusActive = "active"
StatusPending = "pending"
StatusGranted = "granted"
StatusFailed = "failed"
RewardTypeCoin = "coin"
RewardTypeGift = "gift"
RewardTypeProp = "prop"
RewardTypeDress = "dress"
EventTypeWheelRewardSettlement = "WheelRewardSettlement"
)
// RuleConfig 是转盘配置的不可变版本快照。
// 金币和礼物可以配置 RTP 计值,道具/装扮即使有展示价值也必须在运行态归零。
type RuleConfig struct {
AppCode string
WheelID string
RuleVersion int64
Enabled bool
DrawPriceCoins int64
TargetRTPPPM int64
PoolRatePPM int64
SettlementWindowDraws int64
InitialPoolCoins int64
PoolReserveCoins int64
MaxSingleRTPPayout int64
EffectiveFromMS int64
CreatedByAdminID int64
CreatedAtMS int64
Tiers []Tier
}
// Tier 是一个转盘奖档RewardType 决定发放 owner 和 RTP 计值口径。
type Tier struct {
TierID string
DisplayName string
RewardType string
RewardID string
RewardCount int64
RewardCoins int64
RTPValueCoins int64
WeightPPM int64
Enabled bool
MetadataJSON string
}
type DrawCommand struct {
CommandID string
WheelID string
UserID int64
DeviceID string
DrawCount int32
CoinSpent int64
PaidAtMS int64
VisibleRegionID int64
}
type DrawResult struct {
DrawID string
DrawIDs []string
CommandID string
WheelID string
RuleVersion int64
SelectedTierID string
RewardType string
RewardID string
RewardCount int64
RewardCoins int64
RTPValueCoins int64
RewardStatus string
RTPWindowIndex int64
ActualRTPPPM int64
CreatedAtMS int64
WalletTransactionID string
CoinBalanceAfter int64
MetadataJSON string
}
type DrawSummary struct {
WheelID string
TotalDraws int64
UniqueUsers int64
TotalSpentCoins int64
TotalRTPValueCoins int64
ActualRTPPPM int64
PendingDraws int64
GrantedDraws int64
FailedDraws int64
}
type DrawQuery struct {
WheelID string
UserID int64
Status string
Page int32
PageSize int32
}

View File

@ -27,6 +27,7 @@ type Repository interface {
FindCurrentAgencyOpeningCycle(ctx context.Context, nowMS int64) (domain.Cycle, bool, error)
GetAgencyOpeningApplicationByOwner(ctx context.Context, ownerUserID int64) (domain.Application, bool, error)
CreateAgencyOpeningApplication(ctx context.Context, cycle domain.Cycle, agency domain.AgencySnapshot, nowMS int64) (domain.Application, bool, error)
ApproveAgencyOpeningApplication(ctx context.Context, applicationID string, operatorAdminID int64, nowMS int64) (domain.Application, error)
ListAgencyOpeningApplications(ctx context.Context, query domain.ApplicationQuery) ([]domain.Application, int64, error)
ConsumeAgencyOpeningGiftEvent(ctx context.Context, event domain.GiftEvent, nowMS int64) (domain.EventResult, error)
ClaimDueAgencyOpeningCycles(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int32) ([]domain.Cycle, error)
@ -163,6 +164,21 @@ func (s *Service) Apply(ctx context.Context, userID int64, commandID string) (do
return s.repository.CreateAgencyOpeningApplication(ctx, qualification.Cycle, qualification.Agency, s.now().UTC().UnixMilli())
}
func (s *Service) ApproveApplication(ctx context.Context, applicationID string, operatorAdminID int64) (domain.Application, error) {
if err := s.requireRepository(); err != nil {
return domain.Application{}, err
}
applicationID = strings.TrimSpace(applicationID)
if applicationID == "" {
return domain.Application{}, xerr.New(xerr.InvalidArgument, "application_id is required")
}
if operatorAdminID <= 0 {
return domain.Application{}, xerr.New(xerr.InvalidArgument, "operator_admin_id is required")
}
// 开业积分必须从后台同意这一刻重新开始计算,不能把用户申请后、审核前的房间礼物补进活动分。
return s.repository.ApproveAgencyOpeningApplication(ctx, applicationID, operatorAdminID, s.now().UTC().UnixMilli())
}
func (s *Service) ListApplications(ctx context.Context, query domain.ApplicationQuery) ([]domain.Application, int64, error) {
if err := s.requireRepository(); err != nil {
return nil, 0, err

View File

@ -141,6 +141,9 @@ func (r *agencyOpeningFakeRepo) GetAgencyOpeningApplicationByOwner(_ context.Con
func (r *agencyOpeningFakeRepo) CreateAgencyOpeningApplication(context.Context, domain.Cycle, domain.AgencySnapshot, int64) (domain.Application, bool, error) {
return domain.Application{}, false, nil
}
func (r *agencyOpeningFakeRepo) ApproveAgencyOpeningApplication(context.Context, string, int64, int64) (domain.Application, error) {
return domain.Application{}, nil
}
func (r *agencyOpeningFakeRepo) ListAgencyOpeningApplications(context.Context, domain.ApplicationQuery) ([]domain.Application, int64, error) {
return nil, 0, nil
}

View File

@ -236,8 +236,8 @@ func (s *Service) SetUserLevel(ctx context.Context, command domain.SetUserLevelC
if !validTrack(command.Track) {
return domain.SetUserLevelResult{}, xerr.New(xerr.InvalidArgument, "track is invalid")
}
if command.Level < 1 || command.Level > 11 {
return domain.SetUserLevelResult{}, xerr.New(xerr.InvalidArgument, "level must be between 1 and 11")
if command.Level < 1 || command.Level > 30 {
return domain.SetUserLevelResult{}, xerr.New(xerr.InvalidArgument, "level must be between 1 and 30")
}
if command.Reason == "" {
command.Reason = "manager_center_level"

View File

@ -0,0 +1,106 @@
package wheel
import (
"fmt"
"strings"
"hyapp/pkg/xerr"
lotteryengine "hyapp/services/activity-service/internal/domain/lotteryengine"
domain "hyapp/services/activity-service/internal/domain/wheel"
)
const defaultWheelID = "default"
func NormalizeWheelID(wheelID string) string {
wheelID = strings.TrimSpace(wheelID)
if wheelID == "" {
return defaultWheelID
}
return wheelID
}
// NormalizeRuleConfig 是转盘配置发布前的唯一收口。
// 这里强制道具/装扮的 RTP 价值为 0避免后台展示价值、运营估值或资源原价污染真实返奖率。
func NormalizeRuleConfig(config domain.RuleConfig) domain.RuleConfig {
config.AppCode = strings.TrimSpace(config.AppCode)
config.WheelID = NormalizeWheelID(config.WheelID)
normalized := make([]domain.Tier, 0, len(config.Tiers))
for _, tier := range config.Tiers {
tier.TierID = strings.TrimSpace(tier.TierID)
tier.DisplayName = strings.TrimSpace(tier.DisplayName)
tier.RewardType = lotteryengine.NormalizePrizeKind(tier.RewardType)
tier.RewardID = strings.TrimSpace(tier.RewardID)
tier.MetadataJSON = strings.TrimSpace(tier.MetadataJSON)
if tier.MetadataJSON == "" {
tier.MetadataJSON = "{}"
}
if tier.RewardCount <= 0 {
tier.RewardCount = 1
}
tier.RTPValueCoins = lotteryengine.NormalizeRTPValueCoins(tier.RewardType, tier.RTPValueCoins)
normalized = append(normalized, tier)
}
config.Tiers = normalized
return config
}
// ValidateRuleConfig 只验证运行所需的硬边界;概率是否满足产品预期由后台配置页负责展示和二次确认。
func ValidateRuleConfig(config domain.RuleConfig) error {
config = NormalizeRuleConfig(config)
if config.WheelID == "" {
return xerr.New(xerr.InvalidArgument, "wheel_id is required")
}
if config.DrawPriceCoins <= 0 {
return xerr.New(xerr.InvalidArgument, "draw_price_coins must be positive")
}
if config.TargetRTPPPM < 0 {
return xerr.New(xerr.InvalidArgument, "target_rtp_ppm must be non-negative")
}
if config.PoolRatePPM < 0 {
return xerr.New(xerr.InvalidArgument, "pool_rate_ppm must be non-negative")
}
if config.SettlementWindowDraws <= 0 {
return xerr.New(xerr.InvalidArgument, "settlement_window_draws must be positive")
}
if len(config.Tiers) == 0 {
return xerr.New(xerr.InvalidArgument, "wheel tiers are required")
}
seen := map[string]bool{}
var enabled bool
for _, tier := range config.Tiers {
if tier.TierID == "" {
return xerr.New(xerr.InvalidArgument, "wheel tier_id is required")
}
if seen[tier.TierID] {
return xerr.New(xerr.InvalidArgument, fmt.Sprintf("duplicate wheel tier_id: %s", tier.TierID))
}
seen[tier.TierID] = true
if tier.WeightPPM < 0 {
return xerr.New(xerr.InvalidArgument, fmt.Sprintf("wheel tier %s weight_ppm must be non-negative", tier.TierID))
}
switch tier.RewardType {
case lotteryengine.PrizeKindCoin:
if tier.RewardCoins < 0 {
return xerr.New(xerr.InvalidArgument, fmt.Sprintf("wheel tier %s reward_coins must be non-negative", tier.TierID))
}
case lotteryengine.PrizeKindGift:
if tier.RewardID == "" {
return xerr.New(xerr.InvalidArgument, fmt.Sprintf("wheel tier %s gift reward_id is required", tier.TierID))
}
case lotteryengine.PrizeKindProp, lotteryengine.PrizeKindDress:
if tier.RewardID == "" {
return xerr.New(xerr.InvalidArgument, fmt.Sprintf("wheel tier %s prop reward_id is required", tier.TierID))
}
if tier.RTPValueCoins != 0 {
return xerr.New(xerr.InvalidArgument, fmt.Sprintf("wheel tier %s prop rtp_value_coins must be zero", tier.TierID))
}
default:
return xerr.New(xerr.InvalidArgument, fmt.Sprintf("unsupported wheel reward_type: %s", tier.RewardType))
}
enabled = enabled || tier.Enabled
}
if !enabled {
return xerr.New(xerr.InvalidArgument, "wheel enabled tiers are required")
}
return nil
}

View File

@ -0,0 +1,41 @@
package wheel
import (
"testing"
domain "hyapp/services/activity-service/internal/domain/wheel"
)
func TestNormalizeRuleConfigForcesPropRTPValueToZero(t *testing.T) {
config := NormalizeRuleConfig(domain.RuleConfig{
WheelID: "classic",
DrawPriceCoins: 100,
SettlementWindowDraws: 1000,
Tiers: []domain.Tier{
{TierID: "dress", RewardType: "dress", RewardID: "frame_1", RTPValueCoins: 999999, WeightPPM: 1, Enabled: true},
{TierID: "prop", RewardType: "item", RewardID: "car_1", RTPValueCoins: 888888, WeightPPM: 1, Enabled: true},
{TierID: "coin", RewardType: "coin", RewardCoins: 100, RTPValueCoins: 100, WeightPPM: 1, Enabled: true},
},
})
if config.Tiers[0].RTPValueCoins != 0 || config.Tiers[1].RTPValueCoins != 0 {
t.Fatalf("prop and dress rtp values must be zero: %#v", config.Tiers)
}
if config.Tiers[2].RTPValueCoins != 100 {
t.Fatalf("coin rtp value should be preserved: %#v", config.Tiers[2])
}
}
func TestValidateRuleConfigRejectsPropRTPValueAfterNormalizationByCallerMutation(t *testing.T) {
config := NormalizeRuleConfig(domain.RuleConfig{
WheelID: "classic",
DrawPriceCoins: 100,
SettlementWindowDraws: 1000,
Tiers: []domain.Tier{
{TierID: "prop", RewardType: "prop", RewardID: "car_1", WeightPPM: 1, Enabled: true},
},
})
config.Tiers[0].RTPValueCoins = 100
if err := ValidateRuleConfig(config); err != nil {
t.Fatalf("ValidateRuleConfig should normalize prop rtp value before validation: %v", err)
}
}

View File

@ -0,0 +1,176 @@
package wheel
import (
"context"
"log/slog"
"strings"
"time"
"google.golang.org/grpc"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/logx"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/wheel"
)
type Repository interface {
PublishWheelRuleConfig(ctx context.Context, config domain.RuleConfig, nowMS int64) (domain.RuleConfig, error)
GetWheelRuleConfig(ctx context.Context, wheelID string) (domain.RuleConfig, bool, error)
ExecuteWheelDraw(ctx context.Context, cmd domain.DrawCommand, nowMS int64) (domain.DrawResult, error)
ListWheelDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error)
GetWheelDrawSummary(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error)
MarkWheelDrawsGranted(ctx context.Context, appCode string, drawIDs []string, transactionID string, nowMS int64) error
}
type WalletClient interface {
CreditWheelReward(ctx context.Context, req *walletv1.CreditWheelRewardRequest, opts ...grpc.CallOption) (*walletv1.CreditWheelRewardResponse, error)
}
type Service struct {
repository Repository
wallet WalletClient
now func() time.Time
}
type Option func(*Service)
func WithWallet(client WalletClient) Option {
return func(s *Service) {
s.wallet = client
}
}
func New(repository Repository, options ...Option) *Service {
service := &Service{repository: repository, now: time.Now}
for _, option := range options {
if option != nil {
option(service)
}
}
return service
}
func (s *Service) SetClock(now func() time.Time) {
if now != nil {
s.now = now
}
}
func (s *Service) Draw(ctx context.Context, cmd domain.DrawCommand) (domain.DrawResult, error) {
if err := s.requireRepository(); err != nil {
return domain.DrawResult{}, err
}
cmd.CommandID = strings.TrimSpace(cmd.CommandID)
cmd.WheelID = NormalizeWheelID(cmd.WheelID)
cmd.DeviceID = strings.TrimSpace(cmd.DeviceID)
if cmd.DrawCount <= 0 {
cmd.DrawCount = 1
}
if cmd.CommandID == "" || cmd.WheelID == "" || cmd.UserID <= 0 || cmd.DeviceID == "" || cmd.CoinSpent <= 0 {
return domain.DrawResult{}, xerr.New(xerr.InvalidArgument, "wheel draw command is incomplete")
}
if cmd.PaidAtMS <= 0 {
// paid_at_ms 来自钱包扣费事实;缺失时使用 UTC 服务端时间,保证 RTP 窗口和审计时间不受本地时区影响。
cmd.PaidAtMS = s.now().UTC().UnixMilli()
}
result, err := s.repository.ExecuteWheelDraw(ctx, cmd, s.now().UTC().UnixMilli())
if err != nil {
return domain.DrawResult{}, err
}
return s.creditCoinRewardFastPath(ctx, cmd, result), nil
}
func (s *Service) UpsertConfig(ctx context.Context, config domain.RuleConfig) (domain.RuleConfig, error) {
if err := s.requireRepository(); err != nil {
return domain.RuleConfig{}, err
}
config = NormalizeRuleConfig(config)
if err := ValidateRuleConfig(config); err != nil {
return domain.RuleConfig{}, err
}
return s.repository.PublishWheelRuleConfig(ctx, config, s.now().UTC().UnixMilli())
}
func (s *Service) GetConfig(ctx context.Context, wheelID string) (domain.RuleConfig, error) {
if err := s.requireRepository(); err != nil {
return domain.RuleConfig{}, err
}
config, exists, err := s.repository.GetWheelRuleConfig(ctx, NormalizeWheelID(wheelID))
if err != nil {
return domain.RuleConfig{}, err
}
if !exists {
return domain.RuleConfig{}, xerr.New(xerr.NotFound, "wheel config not found")
}
return config, nil
}
func (s *Service) ListDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error) {
if err := s.requireRepository(); err != nil {
return nil, 0, err
}
return s.repository.ListWheelDraws(ctx, normalizeDrawQuery(query))
}
func (s *Service) GetDrawSummary(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error) {
if err := s.requireRepository(); err != nil {
return domain.DrawSummary{}, err
}
return s.repository.GetWheelDrawSummary(ctx, normalizeDrawQuery(query))
}
func (s *Service) creditCoinRewardFastPath(ctx context.Context, cmd domain.DrawCommand, result domain.DrawResult) domain.DrawResult {
if result.RewardType != domain.RewardTypeCoin || result.RewardCoins <= 0 || result.RewardStatus == domain.StatusGranted || len(result.DrawIDs) != 1 {
return result
}
if s.wallet == nil {
logx.Warn(ctx, "wheel_reward_wallet_not_configured", slog.String("draw_id", result.DrawID))
return result
}
resp, err := s.wallet.CreditWheelReward(appcode.WithContext(ctx, appcode.FromContext(ctx)), &walletv1.CreditWheelRewardRequest{
CommandId: "wheel_reward:" + result.DrawID,
AppCode: appcode.FromContext(ctx),
TargetUserId: cmd.UserID,
Amount: result.RewardCoins,
DrawId: result.DrawID,
WheelId: result.WheelID,
SelectedTierId: result.SelectedTierID,
Reason: "wheel_reward",
VisibleRegionId: cmd.VisibleRegionID,
})
if err != nil {
// 钱包不可用不能回滚已经确认的抽奖事实pending 记录和 outbox 保留,后续补偿 worker 可以继续处理。
logx.Error(ctx, "wheel_reward_fast_path_failed", err, slog.String("draw_id", result.DrawID), slog.String("command_id", result.CommandID))
return result
}
result.RewardStatus = domain.StatusGranted
result.WalletTransactionID = resp.GetTransactionId()
result.CoinBalanceAfter = resp.GetBalance().GetAvailableAmount()
if err := s.repository.MarkWheelDrawsGranted(appcode.WithContext(context.WithoutCancel(ctx), appcode.FromContext(ctx)), appcode.FromContext(ctx), result.DrawIDs, result.WalletTransactionID, s.now().UTC().UnixMilli()); err != nil {
logx.Error(ctx, "wheel_reward_fast_path_mark_granted_failed", err, slog.String("draw_id", result.DrawID), slog.String("wallet_transaction_id", result.WalletTransactionID))
}
return result
}
func normalizeDrawQuery(query domain.DrawQuery) domain.DrawQuery {
query.WheelID = NormalizeWheelID(query.WheelID)
query.Status = strings.TrimSpace(query.Status)
if query.Page <= 0 {
query.Page = 1
}
if query.PageSize <= 0 {
query.PageSize = 20
}
if query.PageSize > 100 {
query.PageSize = 100
}
return query
}
func (s *Service) requireRepository() error {
if s == nil || s.repository == nil {
return xerr.New(xerr.Unavailable, "wheel repository is not configured")
}
return nil
}

View File

@ -44,7 +44,7 @@ func (r *Repository) ensureAgencyOpeningTables(ctx context.Context) error {
agency_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '代理名称快照',
host_count INT NOT NULL DEFAULT 0 COMMENT '申请时有效主播数快照',
agency_created_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '代理创建时间快照',
status VARCHAR(32) NOT NULL DEFAULT 'applied' COMMENT 'applied/pending/granted/failed',
status VARCHAR(32) NOT NULL DEFAULT 'applied' COMMENT 'applied/approved/pending/granted/failed/expired',
score_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '历史字段名周期内开业房间贡献值 HeatValue',
reward_rank_no INT NOT NULL DEFAULT 0 COMMENT '结算命中的档位序号',
reward_threshold_coin_spent BIGINT NOT NULL DEFAULT 0 COMMENT '结算命中的流水阈值',
@ -53,6 +53,10 @@ func (r *Repository) ensureAgencyOpeningTables(ctx context.Context) error {
wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包交易 ID',
failure_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '失败原因',
applied_at_ms BIGINT NOT NULL COMMENT '申请时间',
approved_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '后台同意时间',
score_start_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分开始时间',
score_end_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分截止时间',
reviewed_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '审核管理员',
granted_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '发奖时间',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
@ -105,6 +109,34 @@ func (r *Repository) ensureAgencyOpeningRewardTierColumns(ctx context.Context) e
return err
}
}
if exists, err := r.columnExists(ctx, "agency_opening_applications", "approved_at_ms"); err != nil {
return err
} else if !exists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD COLUMN approved_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '后台同意时间' AFTER applied_at_ms`); err != nil {
return err
}
}
if exists, err := r.columnExists(ctx, "agency_opening_applications", "score_start_ms"); err != nil {
return err
} else if !exists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD COLUMN score_start_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分开始时间' AFTER approved_at_ms`); err != nil {
return err
}
}
if exists, err := r.columnExists(ctx, "agency_opening_applications", "score_end_ms"); err != nil {
return err
} else if !exists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD COLUMN score_end_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分截止时间' AFTER score_start_ms`); err != nil {
return err
}
}
if exists, err := r.columnExists(ctx, "agency_opening_applications", "reviewed_by_admin_id"); err != nil {
return err
} else if !exists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD COLUMN reviewed_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '审核管理员' AFTER score_end_ms`); err != nil {
return err
}
}
return nil
}

View File

@ -217,8 +217,8 @@ func (r *Repository) CreateAgencyOpeningApplication(ctx context.Context, cycle d
INSERT IGNORE INTO agency_opening_applications (
app_code, application_id, cycle_id, agency_id, agency_owner_user_id, agency_name, host_count, agency_created_at_ms,
status, score_coin_amount, reward_rank_no, reward_threshold_coin_spent, reward_coin_amount, wallet_command_id, wallet_transaction_id, failure_reason,
applied_at_ms, granted_at_ms, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, 0, '', '', '', ?, 0, ?, ?)`,
applied_at_ms, approved_at_ms, score_start_ms, score_end_ms, reviewed_by_admin_id, granted_at_ms, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, 0, '', '', '', ?, 0, 0, 0, 0, 0, ?, ?)`,
appcode.FromContext(ctx), applicationID, cycle.CycleID, agency.AgencyID, agency.OwnerUserID, agency.Name, agency.HostCount, agency.AgencyCreatedAtMS,
domain.ApplicationStatusApplied, nowMS, nowMS, nowMS)
if err != nil {
@ -229,6 +229,71 @@ func (r *Repository) CreateAgencyOpeningApplication(ctx context.Context, cycle d
return item, affected > 0 && ok, err
}
func (r *Repository) ApproveAgencyOpeningApplication(ctx context.Context, applicationID string, operatorAdminID int64, nowMS int64) (domain.Application, error) {
if r == nil || r.db == nil {
return domain.Application{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return domain.Application{}, err
}
defer tx.Rollback()
var app domain.Application
var cycleEndMS int64
var cycleStatus string
err = tx.QueryRowContext(ctx, agencyOpeningApplicationSelect()+`
WHERE app_code = ? AND application_id = ?
FOR UPDATE`, appcode.FromContext(ctx), strings.TrimSpace(applicationID)).Scan(
&app.AppCode, &app.ApplicationID, &app.CycleID, &app.AgencyID, &app.AgencyOwnerUserID, &app.AgencyName,
&app.HostCount, &app.AgencyCreatedAtMS, &app.Status, &app.ScoreCoinAmount, &app.RewardRankNo, &app.RewardThresholdCoin, &app.RewardCoinAmount,
&app.WalletCommandID, &app.WalletTransactionID, &app.FailureReason, &app.AppliedAtMS, &app.ApprovedAtMS, &app.ScoreStartMS, &app.ScoreEndMS,
&app.ReviewedByAdminID, &app.GrantedAtMS, &app.CreatedAtMS, &app.UpdatedAtMS)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.Application{}, xerr.New(xerr.NotFound, "agency opening application not found")
}
return domain.Application{}, err
}
if err := tx.QueryRowContext(ctx, `
SELECT status, end_ms
FROM agency_opening_cycles
WHERE app_code = ? AND cycle_id = ? AND activity_code = ?`, appcode.FromContext(ctx), app.CycleID, domain.ActivityCode).Scan(&cycleStatus, &cycleEndMS); err != nil {
return domain.Application{}, err
}
if app.Status == domain.ApplicationStatusApproved {
if err := tx.Commit(); err != nil {
return domain.Application{}, err
}
return r.getAgencyOpeningApplication(ctx, app.ApplicationID)
}
if app.Status != domain.ApplicationStatusApplied {
return domain.Application{}, xerr.New(xerr.Conflict, "application is not waiting for approval")
}
if cycleStatus != domain.StatusActive {
return domain.Application{}, xerr.New(xerr.Conflict, "activity is not active")
}
scoreEndMS := nowMS + int64((24 * time.Hour).Milliseconds())
if cycleEndMS > 0 && cycleEndMS < scoreEndMS {
scoreEndMS = cycleEndMS
}
if scoreEndMS <= nowMS {
return domain.Application{}, xerr.New(xerr.Conflict, "activity period is already ended")
}
if _, err := tx.ExecContext(ctx, `
UPDATE agency_opening_applications
SET status = ?, approved_at_ms = ?, score_start_ms = ?, score_end_ms = ?, reviewed_by_admin_id = ?, score_coin_amount = 0,
failure_reason = '', updated_at_ms = ?
WHERE app_code = ? AND application_id = ? AND status = ?`,
domain.ApplicationStatusApproved, nowMS, nowMS, scoreEndMS, operatorAdminID, nowMS,
appcode.FromContext(ctx), app.ApplicationID, domain.ApplicationStatusApplied); err != nil {
return domain.Application{}, err
}
if err := tx.Commit(); err != nil {
return domain.Application{}, err
}
return r.getAgencyOpeningApplication(ctx, app.ApplicationID)
}
func (r *Repository) ListAgencyOpeningApplications(ctx context.Context, query domain.ApplicationQuery) ([]domain.Application, int64, error) {
if r == nil || r.db == nil {
return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
@ -274,17 +339,18 @@ func (r *Repository) ConsumeAgencyOpeningGiftEvent(ctx context.Context, event do
err = tx.QueryRowContext(ctx, `
SELECT app.app_code, app.application_id, app.cycle_id, app.agency_id, app.agency_owner_user_id, app.agency_name, app.host_count, app.agency_created_at_ms,
app.status, app.score_coin_amount, app.reward_rank_no, app.reward_threshold_coin_spent, app.reward_coin_amount, app.wallet_command_id, app.wallet_transaction_id, app.failure_reason,
app.applied_at_ms, app.granted_at_ms, app.created_at_ms, app.updated_at_ms
app.applied_at_ms, app.approved_at_ms, app.score_start_ms, app.score_end_ms, app.reviewed_by_admin_id, app.granted_at_ms, app.created_at_ms, app.updated_at_ms
FROM agency_opening_applications app
INNER JOIN agency_opening_cycles cycle ON cycle.app_code = app.app_code AND cycle.cycle_id = app.cycle_id
WHERE app.app_code = ? AND app.agency_owner_user_id = ? AND app.status IN (?, ?, ?) AND app.applied_at_ms <= ?
WHERE app.app_code = ? AND app.agency_owner_user_id = ? AND app.status = ? AND app.score_start_ms <= ? AND app.score_end_ms > ?
AND cycle.activity_code = ? AND cycle.status IN (?, ?) AND cycle.start_ms <= ? AND cycle.end_ms > ?
ORDER BY app.applied_at_ms DESC
LIMIT 1`, appcode.FromContext(ctx), event.RoomOwnerUserID, domain.ApplicationStatusApplied, domain.ApplicationStatusPending, domain.ApplicationStatusFailed, event.OccurredAtMS,
LIMIT 1`, appcode.FromContext(ctx), event.RoomOwnerUserID, domain.ApplicationStatusApproved, event.OccurredAtMS, event.OccurredAtMS,
domain.ActivityCode, domain.StatusActive, domain.StatusSettling, event.OccurredAtMS, event.OccurredAtMS).Scan(
&app.AppCode, &app.ApplicationID, &app.CycleID, &app.AgencyID, &app.AgencyOwnerUserID, &app.AgencyName,
&app.HostCount, &app.AgencyCreatedAtMS, &app.Status, &app.ScoreCoinAmount, &app.RewardRankNo, &app.RewardThresholdCoin, &app.RewardCoinAmount,
&app.WalletCommandID, &app.WalletTransactionID, &app.FailureReason, &app.AppliedAtMS, &app.GrantedAtMS, &app.CreatedAtMS, &app.UpdatedAtMS)
&app.WalletCommandID, &app.WalletTransactionID, &app.FailureReason, &app.AppliedAtMS, &app.ApprovedAtMS, &app.ScoreStartMS, &app.ScoreEndMS,
&app.ReviewedByAdminID, &app.GrantedAtMS, &app.CreatedAtMS, &app.UpdatedAtMS)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.EventResult{EventID: event.EventID, Status: domain.EventStatusSkipped}, tx.Commit()
@ -370,10 +436,18 @@ func (r *Repository) PrepareAgencyOpeningSettlements(ctx context.Context, cycle
return nil, err
}
defer tx.Rollback()
// 周期结算时仍未后台同意的申请没有有效 24 小时计分窗口,直接过期,不进入发奖队列。
if _, err := tx.ExecContext(ctx, `
UPDATE agency_opening_applications
SET status = ?, failure_reason = 'not approved before cycle ended', updated_at_ms = ?
WHERE app_code = ? AND cycle_id = ? AND status = ?`,
domain.ApplicationStatusExpired, nowMS, appcode.FromContext(ctx), cycle.CycleID, domain.ApplicationStatusApplied); err != nil {
return nil, err
}
rows, err := tx.QueryContext(ctx, agencyOpeningApplicationSelect()+`
WHERE app_code = ? AND cycle_id = ? AND status IN (?, ?)
ORDER BY score_coin_amount DESC, applied_at_ms ASC, agency_id ASC`,
appcode.FromContext(ctx), cycle.CycleID, domain.ApplicationStatusApplied, domain.ApplicationStatusFailed)
appcode.FromContext(ctx), cycle.CycleID, domain.ApplicationStatusApproved, domain.ApplicationStatusFailed)
if err != nil {
return nil, err
}
@ -405,7 +479,7 @@ func (r *Repository) PrepareAgencyOpeningSettlements(ctx context.Context, cycle
WHERE app_code = ? AND application_id = ? AND status IN (?, ?)`,
domain.ApplicationStatusGranted, domain.ApplicationStatusPending,
reward.RankNo, reward.ThresholdCoinSpent, reward.RewardCoinAmount, walletCommandID, nowMS,
appcode.FromContext(ctx), application.ApplicationID, domain.ApplicationStatusApplied, domain.ApplicationStatusFailed); err != nil {
appcode.FromContext(ctx), application.ApplicationID, domain.ApplicationStatusApproved, domain.ApplicationStatusFailed); err != nil {
return nil, err
}
}
@ -578,7 +652,7 @@ func agencyOpeningApplicationWhere(ctx context.Context, query domain.Application
func agencyOpeningApplicationSelect() string {
return `SELECT app_code, application_id, cycle_id, agency_id, agency_owner_user_id, agency_name, host_count, agency_created_at_ms,
status, score_coin_amount, reward_rank_no, reward_threshold_coin_spent, reward_coin_amount, wallet_command_id, wallet_transaction_id, failure_reason,
applied_at_ms, granted_at_ms, created_at_ms, updated_at_ms FROM agency_opening_applications`
applied_at_ms, approved_at_ms, score_start_ms, score_end_ms, reviewed_by_admin_id, granted_at_ms, created_at_ms, updated_at_ms FROM agency_opening_applications`
}
func scanAgencyOpeningCycle(row interface{ Scan(...any) error }) (domain.Cycle, error) {
@ -593,7 +667,8 @@ func scanAgencyOpeningApplication(row interface{ Scan(...any) error }) (domain.A
var item domain.Application
err := row.Scan(&item.AppCode, &item.ApplicationID, &item.CycleID, &item.AgencyID, &item.AgencyOwnerUserID, &item.AgencyName,
&item.HostCount, &item.AgencyCreatedAtMS, &item.Status, &item.ScoreCoinAmount, &item.RewardRankNo, &item.RewardThresholdCoin, &item.RewardCoinAmount,
&item.WalletCommandID, &item.WalletTransactionID, &item.FailureReason, &item.AppliedAtMS, &item.GrantedAtMS, &item.CreatedAtMS, &item.UpdatedAtMS)
&item.WalletCommandID, &item.WalletTransactionID, &item.FailureReason, &item.AppliedAtMS, &item.ApprovedAtMS, &item.ScoreStartMS, &item.ScoreEndMS,
&item.ReviewedByAdminID, &item.GrantedAtMS, &item.CreatedAtMS, &item.UpdatedAtMS)
return item, err
}

View File

@ -0,0 +1,114 @@
package mysql
import (
"context"
"testing"
"hyapp/internal/testutil/mysqlschema"
"hyapp/pkg/appcode"
domain "hyapp/services/activity-service/internal/domain/agencyopening"
)
func TestAgencyOpeningScoresOnlyAfterApprovalWithinWindow(t *testing.T) {
schema := mysqlschema.New(t, mysqlschema.Config{
EnvVar: "ACTIVITY_SERVICE_MYSQL_TEST_DSN",
InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_activity_service.sql"),
DatabasePrefix: "hyapp_activity_agency_opening_test",
})
repository, err := Open(context.Background(), schema.DSN)
if err != nil {
t.Fatalf("open repository: %v", err)
}
t.Cleanup(func() { _ = repository.Close() })
ctx := appcode.WithContext(context.Background(), "lalu")
const (
cycleID = "cycle-window"
applicationID = "application-window"
ownerUserID = int64(42001)
nowMS = int64(10_000)
)
_, err = repository.db.ExecContext(ctx, `
INSERT INTO agency_opening_cycles (
app_code, cycle_id, activity_code, title, status, start_ms, end_ms, min_host_count, max_agency_age_days,
created_by_admin_id, updated_by_admin_id, settled_at_ms, locked_by, lock_until_ms, created_at_ms, updated_at_ms
) VALUES ('lalu', ?, ?, 'window test', ?, 1, ?, 10, 30, 1, 1, 0, '', 0, ?, ?)`,
cycleID, domain.ActivityCode, domain.StatusActive, nowMS+int64((48*60*60*1000)), nowMS, nowMS,
)
if err != nil {
t.Fatalf("seed cycle: %v", err)
}
_, err = repository.db.ExecContext(ctx, `
INSERT INTO agency_opening_applications (
app_code, application_id, cycle_id, agency_id, agency_owner_user_id, agency_name, host_count, agency_created_at_ms,
status, score_coin_amount, reward_rank_no, reward_threshold_coin_spent, reward_coin_amount,
wallet_command_id, wallet_transaction_id, failure_reason, applied_at_ms, approved_at_ms,
score_start_ms, score_end_ms, reviewed_by_admin_id, granted_at_ms, created_at_ms, updated_at_ms
) VALUES ('lalu', ?, ?, 8001, ?, 'Window Agency', 12, 1, ?, 0, 0, 0, 0, '', '', '', ?, 0, 0, 0, 0, 0, ?, ?)`,
applicationID, cycleID, ownerUserID, domain.ApplicationStatusApplied, nowMS-1000, nowMS, nowMS,
)
if err != nil {
t.Fatalf("seed application: %v", err)
}
beforeApproval, err := repository.ConsumeAgencyOpeningGiftEvent(ctx, domain.GiftEvent{
EventID: "gift-before-approval",
RoomOwnerUserID: ownerUserID,
TargetUserID: 99,
CoinSpent: 700,
OccurredAtMS: nowMS + 100,
}, nowMS+100)
if err != nil {
t.Fatalf("consume before approval: %v", err)
}
if beforeApproval.Status != domain.EventStatusSkipped {
t.Fatalf("event before approval must be skipped, got %s", beforeApproval.Status)
}
approved, err := repository.ApproveAgencyOpeningApplication(ctx, applicationID, 77, nowMS+200)
if err != nil {
t.Fatalf("approve application: %v", err)
}
wantScoreEnd := nowMS + 200 + int64((24 * 60 * 60 * 1000))
if approved.Status != domain.ApplicationStatusApproved || approved.ScoreStartMS != nowMS+200 || approved.ScoreEndMS != wantScoreEnd || approved.ReviewedByAdminID != 77 {
t.Fatalf("unexpected approved application: %+v", approved)
}
insideWindow, err := repository.ConsumeAgencyOpeningGiftEvent(ctx, domain.GiftEvent{
EventID: "gift-inside-window",
RoomOwnerUserID: ownerUserID,
TargetUserID: 100,
CoinSpent: 900,
OccurredAtMS: nowMS + 300,
}, nowMS+300)
if err != nil {
t.Fatalf("consume inside window: %v", err)
}
if insideWindow.Status != domain.EventStatusConsumed {
t.Fatalf("event inside approved window must be consumed, got %s", insideWindow.Status)
}
afterWindow, err := repository.ConsumeAgencyOpeningGiftEvent(ctx, domain.GiftEvent{
EventID: "gift-after-window",
RoomOwnerUserID: ownerUserID,
TargetUserID: 101,
CoinSpent: 1200,
OccurredAtMS: wantScoreEnd,
}, wantScoreEnd)
if err != nil {
t.Fatalf("consume after window: %v", err)
}
if afterWindow.Status != domain.EventStatusSkipped {
t.Fatalf("event at score_end must be skipped, got %s", afterWindow.Status)
}
var score int64
if err := repository.db.QueryRowContext(ctx, `
SELECT score_coin_amount FROM agency_opening_applications
WHERE app_code = 'lalu' AND application_id = ?`, applicationID).Scan(&score); err != nil {
t.Fatalf("read final score: %v", err)
}
if score != 900 {
t.Fatalf("only the inside-window event should score, got %d", score)
}
}

View File

@ -2,14 +2,12 @@ package mysql
import (
"context"
crand "crypto/rand"
"crypto/sha1"
"database/sql"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/big"
"sort"
"strings"
"time"
@ -17,6 +15,7 @@ import (
"hyapp/pkg/appcode"
"hyapp/pkg/idgen"
"hyapp/pkg/xerr"
lotteryengine "hyapp/services/activity-service/internal/domain/lotteryengine"
domain "hyapp/services/activity-service/internal/domain/luckygift"
)
@ -2684,7 +2683,6 @@ func (r *Repository) selectLuckyCandidate(config domain.Config, pool string, pla
limited := map[string]bool{}
baseCandidates := make([]luckyCandidate, 0, len(config.Tiers))
var totalWeight int64
for _, tier := range config.Tiers {
// 体验池决定当前用户可见奖档权重必须保持后台配置RTP 缺口只进入监控口径,不能反向改本次概率。
if tier.Pool != pool || !tier.Enabled {
@ -2715,26 +2713,34 @@ func (r *Repository) selectLuckyCandidate(config domain.Config, pool string, pla
})
}
// 正常运行只使用后台配置概率;奖池和风控只删除“本事务不能支付”的候选,不插值、不补缺口。
candidates := luckyConfiguredWeightCandidates(baseCandidates)
for _, candidate := range candidates {
totalWeight += candidate.Weight
}
if len(candidates) == 0 {
if len(baseCandidates) == 0 {
// 全部正奖档都被奖池或风控剪掉时,本次只能落 0 倍;不能强行透支,也不能生成修正档改变概率。
return luckyCandidate{TierID: "no_payable_tier", Source: domain.SourceBaseRTP, Pool: pool}, limited, nil
}
// 使用 crypto/rand 生成随机落点;候选集合已经全部可支付,随机后不再做业务降级。
index, err := luckySecureIndex(totalWeight)
engineCandidates := make([]lotteryengine.Candidate, 0, len(baseCandidates))
candidateByID := make(map[string]luckyCandidate, len(baseCandidates))
for _, candidate := range baseCandidates {
engineCandidates = append(engineCandidates, lotteryengine.Candidate{
ID: candidate.TierID,
Weight: candidate.Weight,
PrizeKind: lotteryengine.PrizeKindCoin,
RTPValueCoins: candidate.BaseReward,
})
candidateByID[candidate.TierID] = candidate
}
// 使用通用引擎生成随机落点;候选集合已经全部可支付,随机后不再做业务降级。
selected, err := lotteryengine.SelectWeighted(engineCandidates)
if err != nil {
return luckyCandidate{}, limited, err
}
for _, candidate := range candidates {
if index < candidate.Weight {
return candidate, limited, nil
if candidate, ok := candidateByID[selected.ID]; ok {
if candidate.Weight <= 0 {
// 通用引擎会把全 0 权重脏数据退化为等权;这里同步返回权重快照,方便后台审计看到实际兜底口径。
candidate.Weight = selected.Weight
}
index -= candidate.Weight
return candidate, limited, nil
}
return candidates[len(candidates)-1], limited, nil
return luckyCandidate{}, limited, xerr.New(xerr.Internal, "lottery engine selected unknown lucky tier")
}
func (r *Repository) getLuckyDrawByCommand(ctx context.Context, tx *sql.Tx, appCode, commandID string, forUpdate bool) (domain.DrawResult, bool, error) {
@ -2817,23 +2823,26 @@ func luckyEquivalentDraws(cumulativeWagerCoins int64, referencePrice int64) int6
}
func luckyConfiguredWeightCandidates(candidates []luckyCandidate) []luckyCandidate {
out := make([]luckyCandidate, 0, len(candidates))
var totalWeight int64
engineCandidates := make([]lotteryengine.Candidate, 0, len(candidates))
byID := make(map[string]luckyCandidate, len(candidates))
for _, candidate := range candidates {
if candidate.Weight <= 0 {
continue
}
totalWeight += candidate.Weight
out = append(out, candidate)
engineCandidates = append(engineCandidates, lotteryengine.Candidate{
ID: candidate.TierID,
Weight: candidate.Weight,
PrizeKind: lotteryengine.PrizeKindCoin,
RTPValueCoins: candidate.BaseReward,
})
byID[candidate.TierID] = candidate
}
if totalWeight > 0 {
return out
normalized, err := lotteryengine.NormalizeCandidates(engineCandidates)
if err != nil {
return nil
}
// 老数据或脏配置可能没有权重;为了让抽奖继续暴露结果而不是空候选,退化成等权随机。
out = candidates[:0]
for _, candidate := range candidates {
candidate.Weight = 1
out = append(out, candidate)
out := make([]luckyCandidate, 0, len(normalized))
for _, candidate := range normalized {
item := byID[candidate.ID]
item.Weight = candidate.Weight
out = append(out, item)
}
return out
}
@ -2959,17 +2968,6 @@ func luckyRTPPPM(wager, payout int64) int64 {
return payout * luckyPPMScale / wager
}
func luckySecureIndex(totalWeight int64) (int64, error) {
if totalWeight <= 0 {
return 0, xerr.New(xerr.InvalidArgument, "candidate weight is empty")
}
n, err := crand.Int(crand.Reader, big.NewInt(totalWeight))
if err != nil {
return 0, err
}
return n.Int64(), nil
}
func luckySQLPlaceholders(count int) string {
// database/sql 不支持把 slice 直接绑定到 IN (?),这里生成受控占位符而不是拼接业务值。
if count <= 0 {

View File

@ -78,6 +78,9 @@ func (r *Repository) Migrate(ctx context.Context) error {
if err := r.ensureDefaultLuckyGiftRules(ctx); err != nil {
return err
}
if err := r.ensureWheelTables(ctx); err != nil {
return err
}
if err := r.ensureRoomTurnoverRewardTables(ctx); err != nil {
return err
}

View File

@ -0,0 +1,817 @@
package mysql
import (
"context"
"crypto/sha1"
"database/sql"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"strings"
"hyapp/pkg/appcode"
"hyapp/pkg/idgen"
"hyapp/pkg/xerr"
lotteryengine "hyapp/services/activity-service/internal/domain/lotteryengine"
domain "hyapp/services/activity-service/internal/domain/wheel"
)
const wheelPPMScale int64 = 1_000_000
type wheelRTPWindow struct {
WheelID string
WindowIndex int64
TargetRTPPPM int64
ControlDraws int64
PaidDraws int64
WagerCoins int64
TargetPayout int64
ActualRTPValue int64
CarryPPM int64
Status string
}
type wheelPool struct {
WheelID string
Balance int64
ReserveFloor int64
TotalIn int64
TotalOut int64
}
type wheelCandidate struct {
Tier domain.Tier
}
// PublishWheelRuleConfig 新增转盘不可变配置版本;历史 draw record 永远引用旧版本,避免后台改配置后审计口径漂移。
func (r *Repository) PublishWheelRuleConfig(ctx context.Context, config domain.RuleConfig, nowMS int64) (domain.RuleConfig, error) {
if r == nil || r.db == nil {
return domain.RuleConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
appCode := appcode.FromContext(ctx)
config.AppCode = appCode
config.WheelID = normalizeWheelID(config.WheelID)
config.Tiers = normalizeWheelTiers(config.Tiers)
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return domain.RuleConfig{}, err
}
defer func() { _ = tx.Rollback() }()
var latest sql.NullInt64
if err := tx.QueryRowContext(ctx, `
SELECT MAX(rule_version)
FROM wheel_rule_versions
WHERE app_code = ? AND wheel_id = ?
FOR UPDATE`,
appCode, config.WheelID,
).Scan(&latest); err != nil {
return domain.RuleConfig{}, err
}
if latest.Valid {
config.RuleVersion = latest.Int64 + 1
} else {
config.RuleVersion = 1
}
if config.EffectiveFromMS <= 0 {
config.EffectiveFromMS = nowMS
}
config.CreatedAtMS = nowMS
if _, err := tx.ExecContext(ctx, `
INSERT INTO wheel_rule_versions (
app_code, wheel_id, rule_version, enabled, draw_price_coins, target_rtp_ppm, pool_rate_ppm,
settlement_window_draws, initial_pool_coins, pool_reserve_coins, max_single_rtp_payout,
effective_from_ms, created_by_admin_id, created_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
appCode, config.WheelID, config.RuleVersion, config.Enabled, config.DrawPriceCoins, config.TargetRTPPPM, config.PoolRatePPM,
config.SettlementWindowDraws, config.InitialPoolCoins, config.PoolReserveCoins, config.MaxSingleRTPPayout,
config.EffectiveFromMS, config.CreatedByAdminID, nowMS,
); err != nil {
return domain.RuleConfig{}, err
}
for _, tier := range config.Tiers {
if _, err := tx.ExecContext(ctx, `
INSERT INTO wheel_prize_tiers (
app_code, wheel_id, rule_version, tier_id, display_name, reward_type, reward_id, reward_count,
reward_coins, rtp_value_coins, weight_ppm, enabled, metadata_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
appCode, config.WheelID, config.RuleVersion, tier.TierID, tier.DisplayName, tier.RewardType, tier.RewardID, tier.RewardCount,
tier.RewardCoins, tier.RTPValueCoins, tier.WeightPPM, tier.Enabled, wheelMetadataJSON(tier.MetadataJSON),
); err != nil {
return domain.RuleConfig{}, err
}
}
if err := tx.Commit(); err != nil {
return domain.RuleConfig{}, err
}
return config, nil
}
func (r *Repository) GetWheelRuleConfig(ctx context.Context, wheelID string) (domain.RuleConfig, bool, error) {
if r == nil || r.db == nil {
return domain.RuleConfig{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
return r.getWheelRuleConfig(ctx, r.db, appcode.FromContext(ctx), normalizeWheelID(wheelID), false)
}
// ExecuteWheelDraw 是转盘线上主事务扣费事实已发生事务内只做命中奖档、RTP 观察、奖池扣减和发放 outbox。
func (r *Repository) ExecuteWheelDraw(ctx context.Context, cmd domain.DrawCommand, nowMS int64) (domain.DrawResult, error) {
if r == nil || r.db == nil {
return domain.DrawResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
cmd.WheelID = normalizeWheelID(cmd.WheelID)
if cmd.DrawCount <= 0 {
cmd.DrawCount = 1
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return domain.DrawResult{}, err
}
defer func() { _ = tx.Rollback() }()
appCode := appcode.FromContext(ctx)
existing, complete, err := r.collectWheelDrawsByCommand(ctx, tx, appCode, cmd.CommandID, cmd.DrawCount)
if err != nil {
return domain.DrawResult{}, err
}
if complete {
return aggregateWheelDrawResults(cmd, existing), nil
}
if len(existing) > 0 {
return domain.DrawResult{}, xerr.New(xerr.Conflict, "partial wheel draw command exists")
}
config, exists, err := r.getWheelRuleConfig(ctx, tx, appCode, cmd.WheelID, true)
if err != nil {
return domain.DrawResult{}, err
}
if !exists || !config.Enabled {
return domain.DrawResult{}, xerr.New(xerr.Conflict, "wheel is disabled")
}
if cmd.CoinSpent <= 0 || cmd.UserID <= 0 || strings.TrimSpace(cmd.CommandID) == "" || strings.TrimSpace(cmd.DeviceID) == "" {
return domain.DrawResult{}, xerr.New(xerr.InvalidArgument, "wheel draw command is incomplete")
}
expectedSpent := config.DrawPriceCoins * int64(cmd.DrawCount)
if cmd.CoinSpent != expectedSpent {
return domain.DrawResult{}, xerr.New(xerr.InvalidArgument, "wheel draw coin_spent does not match current price")
}
if cmd.PaidAtMS <= 0 {
cmd.PaidAtMS = nowMS
}
window, err := r.getOpenWheelRTPWindow(ctx, tx, appCode, config, nowMS)
if err != nil {
return domain.DrawResult{}, err
}
pool, err := r.getOrCreateWheelPool(ctx, tx, appCode, config, nowMS)
if err != nil {
return domain.DrawResult{}, err
}
results := make([]domain.DrawResult, 0, cmd.DrawCount)
var totalPoolIn, totalPoolOut int64
for i := int32(1); i <= cmd.DrawCount; i++ {
unitSpent := luckyDrawUnitSpend(cmd.CoinSpent, cmd.DrawCount, i)
totalPoolIn += unitSpent * config.PoolRatePPM / wheelPPMScale
pool.Balance += unitSpent * config.PoolRatePPM / wheelPPMScale
subCommand := wheelSubCommandID(cmd.CommandID, i, cmd.DrawCount)
result, poolOut, err := r.executeSingleWheelDraw(ctx, tx, appCode, cmd, subCommand, unitSpent, config, &window, &pool, nowMS)
if err != nil {
return domain.DrawResult{}, err
}
totalPoolOut += poolOut
results = append(results, result)
}
if err := r.persistWheelPoolDelta(ctx, tx, appCode, config.WheelID, totalPoolIn, totalPoolOut, nowMS); err != nil {
return domain.DrawResult{}, err
}
if err := r.updateWheelStats(ctx, tx, appCode, config.WheelID, cmd.UserID, cmd.CoinSpent, results, nowMS); err != nil {
return domain.DrawResult{}, err
}
if err := tx.Commit(); err != nil {
return domain.DrawResult{}, err
}
return aggregateWheelDrawResults(cmd, results), nil
}
func (r *Repository) ListWheelDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error) {
if r == nil || r.db == nil {
return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
whereSQL, args := wheelDrawWhereClause(appcode.FromContext(ctx), query)
var total int64
if err := r.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM wheel_draw_records WHERE `+whereSQL, args...).Scan(&total); err != nil {
return nil, 0, err
}
page, pageSize := normalizeWheelPage(query.Page, query.PageSize)
args = append(args, int(pageSize), int((page-1)*pageSize))
rows, err := r.db.QueryContext(ctx, `
SELECT draw_id, command_id, wheel_id, rule_version, selected_tier_id, reward_type, reward_id,
reward_count, reward_coins, rtp_value_coins, reward_status, reward_transaction_id,
rtp_window_index, created_at_ms, COALESCE(CAST(prize_metadata_json AS CHAR), '{}')
FROM wheel_draw_records
WHERE `+whereSQL+`
ORDER BY created_at_ms DESC, draw_id DESC
LIMIT ? OFFSET ?`, args...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
items := make([]domain.DrawResult, 0, pageSize)
for rows.Next() {
var item domain.DrawResult
if err := rows.Scan(&item.DrawID, &item.CommandID, &item.WheelID, &item.RuleVersion, &item.SelectedTierID, &item.RewardType, &item.RewardID,
&item.RewardCount, &item.RewardCoins, &item.RTPValueCoins, &item.RewardStatus, &item.WalletTransactionID,
&item.RTPWindowIndex, &item.CreatedAtMS, &item.MetadataJSON); err != nil {
return nil, 0, err
}
item.DrawIDs = []string{item.DrawID}
items = append(items, item)
}
return items, total, rows.Err()
}
func (r *Repository) GetWheelDrawSummary(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error) {
if r == nil || r.db == nil {
return domain.DrawSummary{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
if query.UserID <= 0 && strings.TrimSpace(query.Status) == "" {
return r.getWheelDrawSummaryFromStats(ctx, query)
}
return r.getWheelDrawSummaryFromRecords(ctx, query)
}
func (r *Repository) getWheelDrawSummaryFromRecords(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error) {
whereSQL, args := wheelDrawWhereClause(appcode.FromContext(ctx), query)
var summary domain.DrawSummary
summary.WheelID = normalizeWheelID(query.WheelID)
if err := r.db.QueryRowContext(ctx, `
SELECT
COUNT(*),
COUNT(DISTINCT user_id),
COALESCE(SUM(coin_spent), 0),
COALESCE(SUM(rtp_value_coins), 0),
COALESCE(SUM(CASE WHEN reward_status = 'pending' THEN 1 ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN reward_status = 'granted' THEN 1 ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN reward_status = 'failed' THEN 1 ELSE 0 END), 0)
FROM wheel_draw_records
WHERE `+whereSQL, args...).Scan(
&summary.TotalDraws,
&summary.UniqueUsers,
&summary.TotalSpentCoins,
&summary.TotalRTPValueCoins,
&summary.PendingDraws,
&summary.GrantedDraws,
&summary.FailedDraws,
); err != nil {
return domain.DrawSummary{}, err
}
summary.ActualRTPPPM = wheelRTPPPM(summary.TotalSpentCoins, summary.TotalRTPValueCoins)
return summary, nil
}
func (r *Repository) getWheelDrawSummaryFromStats(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error) {
var summary domain.DrawSummary
summary.WheelID = normalizeWheelID(query.WheelID)
err := r.db.QueryRowContext(ctx, `
SELECT total_draws, unique_users, total_spent_coins, total_rtp_value_coins,
pending_draws, granted_draws, failed_draws
FROM wheel_draw_stats
WHERE app_code = ? AND wheel_id = ?`,
appcode.FromContext(ctx), summary.WheelID,
).Scan(
&summary.TotalDraws,
&summary.UniqueUsers,
&summary.TotalSpentCoins,
&summary.TotalRTPValueCoins,
&summary.PendingDraws,
&summary.GrantedDraws,
&summary.FailedDraws,
)
if errors.Is(err, sql.ErrNoRows) {
return summary, nil
}
if err != nil {
return domain.DrawSummary{}, err
}
summary.ActualRTPPPM = wheelRTPPPM(summary.TotalSpentCoins, summary.TotalRTPValueCoins)
return summary, nil
}
func (r *Repository) MarkWheelDrawsGranted(ctx context.Context, appCode string, drawIDs []string, transactionID string, nowMS int64) error {
if r == nil || r.db == nil {
return xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
appCode = appcode.Normalize(appCode)
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
for _, drawID := range drawIDs {
drawID = strings.TrimSpace(drawID)
if drawID == "" {
continue
}
var wheelID, oldStatus string
if err := tx.QueryRowContext(ctx, `
SELECT wheel_id, reward_status
FROM wheel_draw_records
WHERE app_code = ? AND draw_id = ?
FOR UPDATE`,
appCode, drawID,
).Scan(&wheelID, &oldStatus); err != nil {
if errors.Is(err, sql.ErrNoRows) {
continue
}
return err
}
if oldStatus != domain.StatusGranted {
if _, err := tx.ExecContext(ctx, `
UPDATE wheel_draw_records
SET reward_status = 'granted', reward_transaction_id = ?, reward_failure_reason = '', updated_at_ms = ?
WHERE app_code = ? AND draw_id = ? AND reward_status <> 'granted'`,
transactionID, nowMS, appCode, drawID,
); err != nil {
return err
}
// 转盘统计是 draw 事务内即时累加的,后续发奖状态变化必须同步搬移计数,避免后台看到永久 pending。
if _, err := tx.ExecContext(ctx, `
UPDATE wheel_draw_stats
SET pending_draws = CASE WHEN pending_draws > 0 THEN pending_draws - 1 ELSE 0 END,
granted_draws = granted_draws + 1,
updated_at_ms = ?
WHERE app_code = ? AND wheel_id = ?`,
nowMS, appCode, wheelID,
); err != nil {
return err
}
}
if _, err := tx.ExecContext(ctx, `
UPDATE activity_outbox
SET status = 'delivered', locked_by = '', lock_until_ms = 0, last_error = '', updated_at_ms = ?
WHERE app_code = ? AND outbox_id = ?`,
nowMS, appCode, "wheel_reward_"+drawID,
); err != nil {
return err
}
}
return tx.Commit()
}
func (r *Repository) executeSingleWheelDraw(ctx context.Context, tx *sql.Tx, appCode string, cmd domain.DrawCommand, commandID string, unitSpent int64, config domain.RuleConfig, window *wheelRTPWindow, pool *wheelPool, nowMS int64) (domain.DrawResult, int64, error) {
candidates, limited := wheelPayableCandidates(config, pool)
if len(candidates) == 0 {
return domain.DrawResult{}, 0, xerr.New(xerr.Conflict, "wheel has no payable tier")
}
engineCandidates := make([]lotteryengine.Candidate, 0, len(candidates))
byID := make(map[string]domain.Tier, len(candidates))
for _, candidate := range candidates {
engineCandidates = append(engineCandidates, lotteryengine.Candidate{
ID: candidate.Tier.TierID,
Weight: candidate.Tier.WeightPPM,
PrizeKind: candidate.Tier.RewardType,
RTPValueCoins: candidate.Tier.RTPValueCoins,
})
byID[candidate.Tier.TierID] = candidate.Tier
}
selected, err := lotteryengine.SelectWeighted(engineCandidates)
if err != nil {
return domain.DrawResult{}, 0, err
}
tier := byID[selected.ID]
tier.RTPValueCoins = selected.RTPValueCoins
pool.Balance -= tier.RTPValueCoins
pool.TotalOut += tier.RTPValueCoins
window.PaidDraws++
window.WagerCoins += unitSpent
window.TargetPayout = window.WagerCoins * window.TargetRTPPPM / wheelPPMScale
window.ActualRTPValue += tier.RTPValueCoins
if _, err := tx.ExecContext(ctx, `
UPDATE wheel_rtp_windows
SET paid_draws = ?, wager_coins = ?, target_payout_coins = ?, actual_rtp_value_coins = ?,
carry_ppm = ?, updated_at_ms = ?
WHERE app_code = ? AND wheel_id = ? AND window_index = ?`,
window.PaidDraws, window.WagerCoins, window.TargetPayout, window.ActualRTPValue,
window.CarryPPM, nowMS, appCode, config.WheelID, window.WindowIndex,
); err != nil {
return domain.DrawResult{}, 0, err
}
drawID := idgen.New("wheel_draw")
status := domain.StatusGranted
if wheelDrawNeedsFulfillment(tier) {
status = domain.StatusPending
}
candidateJSON, _ := json.Marshal(map[string]any{"selected": tier.TierID, "limited": limited})
rtpJSON, _ := json.Marshal(map[string]any{"window_index": window.WindowIndex, "wager_coins": window.WagerCoins, "actual_rtp_value_coins": window.ActualRTPValue})
if _, err := tx.ExecContext(ctx, `
INSERT INTO wheel_draw_records (
app_code, draw_id, command_id, user_id, device_id, visible_region_id, wheel_id, coin_spent,
rule_version, rtp_window_index, selected_tier_id, reward_type, reward_id, reward_count,
reward_coins, rtp_value_coins, candidate_tiers_json, rtp_snapshot_json, prize_metadata_json,
reward_status, paid_at_ms, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
appCode, drawID, commandID, cmd.UserID, cmd.DeviceID, cmd.VisibleRegionID, config.WheelID, unitSpent,
config.RuleVersion, window.WindowIndex, tier.TierID, tier.RewardType, tier.RewardID, tier.RewardCount,
tier.RewardCoins, tier.RTPValueCoins, string(candidateJSON), string(rtpJSON), wheelMetadataJSON(tier.MetadataJSON),
status, cmd.PaidAtMS, nowMS, nowMS,
); err != nil {
return domain.DrawResult{}, 0, err
}
if status == domain.StatusPending {
if err := r.insertWheelRewardOutbox(ctx, tx, appCode, drawID, commandID, cmd, tier, nowMS); err != nil {
return domain.DrawResult{}, 0, err
}
}
return domain.DrawResult{
DrawID: drawID,
CommandID: commandID,
WheelID: config.WheelID,
RuleVersion: config.RuleVersion,
SelectedTierID: tier.TierID,
RewardType: tier.RewardType,
RewardID: tier.RewardID,
RewardCount: tier.RewardCount,
RewardCoins: tier.RewardCoins,
RTPValueCoins: tier.RTPValueCoins,
RewardStatus: status,
RTPWindowIndex: window.WindowIndex,
ActualRTPPPM: wheelRTPPPM(window.WagerCoins, window.ActualRTPValue),
CreatedAtMS: nowMS,
MetadataJSON: wheelMetadataJSON(tier.MetadataJSON),
}, tier.RTPValueCoins, nil
}
func (r *Repository) getWheelRuleConfig(ctx context.Context, queryer interface {
QueryRowContext(context.Context, string, ...any) *sql.Row
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
}, appCode, wheelID string, forUpdate bool) (domain.RuleConfig, bool, error) {
lockSQL := ""
if forUpdate {
lockSQL = " FOR UPDATE"
}
row := queryer.QueryRowContext(ctx, `
SELECT app_code, wheel_id, rule_version, enabled, draw_price_coins, target_rtp_ppm, pool_rate_ppm,
settlement_window_draws, initial_pool_coins, pool_reserve_coins, max_single_rtp_payout,
effective_from_ms, created_by_admin_id, created_at_ms
FROM wheel_rule_versions
WHERE app_code = ? AND wheel_id = ? AND enabled = 1
ORDER BY rule_version DESC
LIMIT 1`+lockSQL,
appCode, wheelID,
)
var config domain.RuleConfig
if err := row.Scan(&config.AppCode, &config.WheelID, &config.RuleVersion, &config.Enabled, &config.DrawPriceCoins, &config.TargetRTPPPM, &config.PoolRatePPM,
&config.SettlementWindowDraws, &config.InitialPoolCoins, &config.PoolReserveCoins, &config.MaxSingleRTPPayout,
&config.EffectiveFromMS, &config.CreatedByAdminID, &config.CreatedAtMS); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.RuleConfig{}, false, nil
}
return domain.RuleConfig{}, false, err
}
rows, err := queryer.QueryContext(ctx, `
SELECT tier_id, display_name, reward_type, reward_id, reward_count, reward_coins, rtp_value_coins, weight_ppm, enabled,
COALESCE(CAST(metadata_json AS CHAR), '{}')
FROM wheel_prize_tiers
WHERE app_code = ? AND wheel_id = ? AND rule_version = ?
ORDER BY tier_id`,
appCode, config.WheelID, config.RuleVersion,
)
if err != nil {
return domain.RuleConfig{}, false, err
}
defer rows.Close()
for rows.Next() {
var tier domain.Tier
if err := rows.Scan(&tier.TierID, &tier.DisplayName, &tier.RewardType, &tier.RewardID, &tier.RewardCount, &tier.RewardCoins, &tier.RTPValueCoins, &tier.WeightPPM, &tier.Enabled, &tier.MetadataJSON); err != nil {
return domain.RuleConfig{}, false, err
}
config.Tiers = append(config.Tiers, tier)
}
if err := rows.Err(); err != nil {
return domain.RuleConfig{}, false, err
}
return config, true, nil
}
func (r *Repository) getOpenWheelRTPWindow(ctx context.Context, tx *sql.Tx, appCode string, config domain.RuleConfig, nowMS int64) (wheelRTPWindow, error) {
window, exists, err := r.getLatestWheelRTPWindow(ctx, tx, appCode, config.WheelID, true)
if err != nil {
return wheelRTPWindow{}, err
}
if !exists {
return r.createWheelRTPWindow(ctx, tx, appCode, config.WheelID, 1, 0, config.SettlementWindowDraws, config.TargetRTPPPM, nowMS)
}
if window.Status == "open" && window.PaidDraws < window.ControlDraws {
return window, nil
}
if window.Status == "open" {
if _, err := tx.ExecContext(ctx, `UPDATE wheel_rtp_windows SET status = 'closed', updated_at_ms = ? WHERE app_code = ? AND wheel_id = ? AND window_index = ?`, nowMS, appCode, config.WheelID, window.WindowIndex); err != nil {
return wheelRTPWindow{}, err
}
}
return r.createWheelRTPWindow(ctx, tx, appCode, config.WheelID, window.WindowIndex+1, window.CarryPPM, config.SettlementWindowDraws, config.TargetRTPPPM, nowMS)
}
func (r *Repository) getLatestWheelRTPWindow(ctx context.Context, tx *sql.Tx, appCode, wheelID string, forUpdate bool) (wheelRTPWindow, bool, error) {
lockSQL := ""
if forUpdate {
lockSQL = " FOR UPDATE"
}
row := tx.QueryRowContext(ctx, `
SELECT wheel_id, window_index, target_rtp_ppm, control_window_draws, paid_draws, wager_coins,
target_payout_coins, actual_rtp_value_coins, carry_ppm, status
FROM wheel_rtp_windows
WHERE app_code = ? AND wheel_id = ?
ORDER BY window_index DESC LIMIT 1`+lockSQL,
appCode, wheelID,
)
var window wheelRTPWindow
if err := row.Scan(&window.WheelID, &window.WindowIndex, &window.TargetRTPPPM, &window.ControlDraws, &window.PaidDraws, &window.WagerCoins,
&window.TargetPayout, &window.ActualRTPValue, &window.CarryPPM, &window.Status); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return wheelRTPWindow{}, false, nil
}
return wheelRTPWindow{}, false, err
}
return window, true, nil
}
func (r *Repository) createWheelRTPWindow(ctx context.Context, tx *sql.Tx, appCode, wheelID string, index, carry, windowDraws, targetPPM, nowMS int64) (wheelRTPWindow, error) {
if windowDraws <= 0 {
windowDraws = 1
}
window := wheelRTPWindow{WheelID: wheelID, WindowIndex: index, TargetRTPPPM: targetPPM, ControlDraws: windowDraws, CarryPPM: carry, Status: "open"}
if _, err := tx.ExecContext(ctx, `
INSERT INTO wheel_rtp_windows (
app_code, wheel_id, window_index, target_rtp_ppm, control_window_draws,
paid_draws, wager_coins, target_payout_coins, actual_rtp_value_coins, carry_ppm, status, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, 0, 0, 0, 0, ?, 'open', ?, ?)`,
appCode, wheelID, index, targetPPM, windowDraws, carry, nowMS, nowMS,
); err != nil {
return wheelRTPWindow{}, err
}
return window, nil
}
func (r *Repository) getOrCreateWheelPool(ctx context.Context, tx *sql.Tx, appCode string, config domain.RuleConfig, nowMS int64) (wheelPool, error) {
row := tx.QueryRowContext(ctx, `SELECT wheel_id, balance, reserve_floor, total_in, total_out FROM wheel_pools WHERE app_code = ? AND wheel_id = ? FOR UPDATE`, appCode, config.WheelID)
var pool wheelPool
if err := row.Scan(&pool.WheelID, &pool.Balance, &pool.ReserveFloor, &pool.TotalIn, &pool.TotalOut); err != nil {
if !errors.Is(err, sql.ErrNoRows) {
return wheelPool{}, err
}
pool = wheelPool{WheelID: config.WheelID, Balance: config.InitialPoolCoins, ReserveFloor: config.PoolReserveCoins}
if _, err := tx.ExecContext(ctx, `INSERT INTO wheel_pools (app_code, wheel_id, balance, reserve_floor, total_in, total_out, created_at_ms, updated_at_ms) VALUES (?, ?, ?, ?, 0, 0, ?, ?)`,
appCode, pool.WheelID, pool.Balance, pool.ReserveFloor, nowMS, nowMS); err != nil {
return wheelPool{}, err
}
}
return pool, nil
}
func (r *Repository) persistWheelPoolDelta(ctx context.Context, tx *sql.Tx, appCode, wheelID string, in, out, nowMS int64) error {
_, err := tx.ExecContext(ctx, `
UPDATE wheel_pools
SET balance = balance + ? - ?, total_in = total_in + ?, total_out = total_out + ?, updated_at_ms = ?
WHERE app_code = ? AND wheel_id = ?`,
in, out, in, out, nowMS, appCode, wheelID,
)
return err
}
func wheelPayableCandidates(config domain.RuleConfig, pool *wheelPool) ([]wheelCandidate, map[string]bool) {
limited := map[string]bool{}
capacity := pool.Balance - pool.ReserveFloor
if capacity < 0 {
capacity = 0
}
if config.MaxSingleRTPPayout > 0 && config.MaxSingleRTPPayout < capacity {
capacity = config.MaxSingleRTPPayout
}
candidates := make([]wheelCandidate, 0, len(config.Tiers))
for _, tier := range config.Tiers {
if !tier.Enabled {
continue
}
tier.RewardType = lotteryengine.NormalizePrizeKind(tier.RewardType)
tier.RTPValueCoins = lotteryengine.NormalizeRTPValueCoins(tier.RewardType, tier.RTPValueCoins)
if tier.RTPValueCoins > capacity {
limited["pool_cap"] = true
continue
}
candidates = append(candidates, wheelCandidate{Tier: tier})
}
return candidates, limited
}
func (r *Repository) updateWheelStats(ctx context.Context, tx *sql.Tx, appCode, wheelID string, userID int64, totalSpent int64, results []domain.DrawResult, nowMS int64) error {
var spent, rtpValue, pending, granted, failed int64
spent = totalSpent
for _, result := range results {
rtpValue += result.RTPValueCoins
switch result.RewardStatus {
case domain.StatusPending:
pending++
case domain.StatusFailed:
failed++
default:
granted++
}
}
if _, err := tx.ExecContext(ctx, `INSERT IGNORE INTO wheel_draw_stat_users (app_code, wheel_id, user_id, created_at_ms) VALUES (?, ?, ?, ?)`, appCode, wheelID, userID, nowMS); err != nil {
return err
}
var uniqueDelta int64
if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM wheel_draw_stat_users WHERE app_code = ? AND wheel_id = ?`, appCode, wheelID).Scan(&uniqueDelta); err != nil {
return err
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO wheel_draw_stats (
app_code, wheel_id, total_draws, unique_users, total_spent_coins, total_rtp_value_coins,
pending_draws, granted_draws, failed_draws, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
total_draws = total_draws + VALUES(total_draws),
unique_users = ?,
total_spent_coins = total_spent_coins + VALUES(total_spent_coins),
total_rtp_value_coins = total_rtp_value_coins + VALUES(total_rtp_value_coins),
pending_draws = pending_draws + VALUES(pending_draws),
granted_draws = granted_draws + VALUES(granted_draws),
failed_draws = failed_draws + VALUES(failed_draws),
updated_at_ms = VALUES(updated_at_ms)`,
appCode, wheelID, int64(len(results)), uniqueDelta, spent, rtpValue, pending, granted, failed, nowMS, nowMS, uniqueDelta,
); err != nil {
return err
}
return nil
}
func (r *Repository) insertWheelRewardOutbox(ctx context.Context, tx *sql.Tx, appCode, drawID, commandID string, cmd domain.DrawCommand, tier domain.Tier, nowMS int64) error {
payload, _ := json.Marshal(map[string]any{
"app_code": appCode,
"draw_id": drawID,
"command_id": commandID,
"user_id": cmd.UserID,
"wheel_id": cmd.WheelID,
"selected_tier_id": tier.TierID,
"reward_type": tier.RewardType,
"reward_id": tier.RewardID,
"reward_count": tier.RewardCount,
"reward_coins": tier.RewardCoins,
"visible_region_id": cmd.VisibleRegionID,
"created_at_ms": nowMS,
})
return r.insertLuckyDrawOutbox(ctx, tx, appCode, "wheel_reward_"+drawID, domain.EventTypeWheelRewardSettlement, payload, nowMS)
}
func (r *Repository) collectWheelDrawsByCommand(ctx context.Context, tx *sql.Tx, appCode, commandID string, drawCount int32) ([]domain.DrawResult, bool, error) {
if drawCount <= 0 {
drawCount = 1
}
results := make([]domain.DrawResult, 0, drawCount)
for i := int32(1); i <= drawCount; i++ {
sub := wheelSubCommandID(commandID, i, drawCount)
row := tx.QueryRowContext(ctx, `
SELECT draw_id, command_id, wheel_id, rule_version, selected_tier_id, reward_type, reward_id, reward_count,
reward_coins, rtp_value_coins, reward_status, reward_transaction_id, rtp_window_index, created_at_ms,
COALESCE(CAST(prize_metadata_json AS CHAR), '{}')
FROM wheel_draw_records
WHERE app_code = ? AND command_id = ?`,
appCode, sub,
)
var result domain.DrawResult
if err := row.Scan(&result.DrawID, &result.CommandID, &result.WheelID, &result.RuleVersion, &result.SelectedTierID, &result.RewardType, &result.RewardID, &result.RewardCount,
&result.RewardCoins, &result.RTPValueCoins, &result.RewardStatus, &result.WalletTransactionID, &result.RTPWindowIndex, &result.CreatedAtMS, &result.MetadataJSON); err != nil {
if errors.Is(err, sql.ErrNoRows) {
continue
}
return nil, false, err
}
results = append(results, result)
}
return results, int32(len(results)) == drawCount, nil
}
func aggregateWheelDrawResults(cmd domain.DrawCommand, results []domain.DrawResult) domain.DrawResult {
if len(results) == 0 {
return domain.DrawResult{}
}
aggregate := results[0]
aggregate.CommandID = cmd.CommandID
aggregate.DrawIDs = make([]string, 0, len(results))
if len(results) == 1 {
aggregate.DrawIDs = []string{aggregate.DrawID}
return aggregate
}
aggregate.SelectedTierID = "batch"
aggregate.RewardType = "batch"
aggregate.RewardID = ""
aggregate.RewardCount = 0
aggregate.RewardCoins = 0
aggregate.RTPValueCoins = 0
aggregate.RewardStatus = domain.StatusGranted
for _, result := range results {
aggregate.DrawIDs = append(aggregate.DrawIDs, result.DrawID)
aggregate.RewardCoins += result.RewardCoins
aggregate.RTPValueCoins += result.RTPValueCoins
if result.RewardStatus == domain.StatusPending {
aggregate.RewardStatus = domain.StatusPending
} else if aggregate.RewardStatus != domain.StatusPending && result.RewardStatus == domain.StatusFailed {
aggregate.RewardStatus = domain.StatusFailed
}
}
return aggregate
}
func wheelSubCommandID(commandID string, drawIndex int32, drawCount int32) string {
if drawCount <= 1 {
return strings.TrimSpace(commandID)
}
suffix := fmt.Sprintf("#%06d", drawIndex)
const maxCommandIDLength = 128
commandID = strings.TrimSpace(commandID)
if len(commandID)+len(suffix) <= maxCommandIDLength {
return commandID + suffix
}
sum := sha1.Sum([]byte(commandID))
hash := hex.EncodeToString(sum[:])[:10]
hashSuffix := "#" + hash + suffix
headLen := maxCommandIDLength - len(hashSuffix)
if headLen < 0 {
headLen = 0
}
if len(commandID) > headLen {
commandID = commandID[:headLen]
}
return commandID + hashSuffix
}
func normalizeWheelID(wheelID string) string {
wheelID = strings.TrimSpace(wheelID)
if wheelID == "" {
return "default"
}
return wheelID
}
func normalizeWheelTiers(tiers []domain.Tier) []domain.Tier {
out := make([]domain.Tier, 0, len(tiers))
for _, tier := range tiers {
tier.TierID = strings.TrimSpace(tier.TierID)
tier.RewardType = lotteryengine.NormalizePrizeKind(tier.RewardType)
tier.RTPValueCoins = lotteryengine.NormalizeRTPValueCoins(tier.RewardType, tier.RTPValueCoins)
tier.MetadataJSON = wheelMetadataJSON(tier.MetadataJSON)
if tier.RewardCount <= 0 {
tier.RewardCount = 1
}
out = append(out, tier)
}
return out
}
func wheelDrawNeedsFulfillment(tier domain.Tier) bool {
return tier.RewardCoins > 0 || strings.TrimSpace(tier.RewardID) != ""
}
func wheelMetadataJSON(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" || !json.Valid([]byte(raw)) {
return "{}"
}
return raw
}
func wheelRTPPPM(wager, payout int64) int64 {
if wager <= 0 {
return 0
}
return payout * wheelPPMScale / wager
}
func wheelDrawWhereClause(appCode string, query domain.DrawQuery) (string, []any) {
where := []string{"app_code = ?"}
args := []any{appCode}
if query.WheelID != "" {
where = append(where, "wheel_id = ?")
args = append(args, normalizeWheelID(query.WheelID))
}
if query.UserID > 0 {
where = append(where, "user_id = ?")
args = append(args, query.UserID)
}
if query.Status != "" {
where = append(where, "reward_status = ?")
args = append(args, strings.TrimSpace(query.Status))
}
return strings.Join(where, " AND "), args
}
func normalizeWheelPage(page int32, pageSize int32) (int32, int32) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
if pageSize > 100 {
pageSize = 100
}
return page, pageSize
}

View File

@ -0,0 +1,139 @@
package mysql
import (
"context"
"hyapp/pkg/xerr"
)
func (r *Repository) ensureWheelTables(ctx context.Context) error {
if r == nil || r.db == nil {
return xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
statements := []string{
`CREATE TABLE IF NOT EXISTS wheel_rule_versions (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
rule_version BIGINT NOT NULL COMMENT '不可变规则版本',
enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用',
draw_price_coins BIGINT NOT NULL COMMENT '单抽价格金币',
target_rtp_ppm BIGINT NOT NULL COMMENT '金币和礼物 RTP 目标ppm',
pool_rate_ppm BIGINT NOT NULL COMMENT '每笔消耗进入转盘基础奖池比例ppm',
settlement_window_draws BIGINT NOT NULL COMMENT 'RTP 观察窗口抽数',
initial_pool_coins BIGINT NOT NULL DEFAULT 0 COMMENT '初始奖池水位',
pool_reserve_coins BIGINT NOT NULL DEFAULT 0 COMMENT '奖池安全水位',
max_single_rtp_payout BIGINT NOT NULL DEFAULT 0 COMMENT '单次计入 RTP 的最大返奖',
effective_from_ms BIGINT NOT NULL COMMENT '生效时间UTC epoch ms',
created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '发布人',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id, rule_version),
KEY idx_wheel_rule_versions_latest (app_code, wheel_id, rule_version),
KEY idx_wheel_rule_versions_enabled (app_code, enabled, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘规则版本表'`,
`CREATE TABLE IF NOT EXISTS wheel_prize_tiers (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
rule_version BIGINT NOT NULL COMMENT '规则版本',
tier_id VARCHAR(96) NOT NULL COMMENT '奖档 ID',
display_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '展示名称快照',
reward_type VARCHAR(32) NOT NULL COMMENT 'coin/gift/prop/dress',
reward_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '礼物或道具资源 ID',
reward_count BIGINT NOT NULL DEFAULT 1 COMMENT '奖励数量',
reward_coins BIGINT NOT NULL DEFAULT 0 COMMENT '金币奖励金额',
rtp_value_coins BIGINT NOT NULL DEFAULT 0 COMMENT '计入 RTP 的奖励价值道具装扮强制为 0',
weight_ppm BIGINT NOT NULL COMMENT '随机权重ppm 口径',
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
metadata_json JSON NOT NULL COMMENT '奖品展示和发放扩展快照',
PRIMARY KEY (app_code, wheel_id, rule_version, tier_id),
KEY idx_wheel_prize_tiers_rule (app_code, wheel_id, rule_version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘奖档表'`,
`CREATE TABLE IF NOT EXISTS wheel_rtp_windows (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
window_index BIGINT NOT NULL COMMENT 'RTP 窗口序号',
target_rtp_ppm BIGINT NOT NULL COMMENT '窗口目标 RTP',
control_window_draws BIGINT NOT NULL COMMENT '窗口抽数',
paid_draws BIGINT NOT NULL DEFAULT 0 COMMENT '已抽次数',
wager_coins BIGINT NOT NULL DEFAULT 0 COMMENT '消耗金币',
target_payout_coins BIGINT NOT NULL DEFAULT 0 COMMENT '目标返奖',
actual_rtp_value_coins BIGINT NOT NULL DEFAULT 0 COMMENT '实际计入 RTP 的返奖',
carry_ppm BIGINT NOT NULL DEFAULT 0 COMMENT '整数除法余数',
status VARCHAR(32) NOT NULL DEFAULT 'open' COMMENT 'open/closed',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id, window_index),
KEY idx_wheel_rtp_windows_open (app_code, wheel_id, status, window_index)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘 RTP 观察窗口'`,
`CREATE TABLE IF NOT EXISTS wheel_pools (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
balance BIGINT NOT NULL DEFAULT 0 COMMENT '当前可用水位',
reserve_floor BIGINT NOT NULL DEFAULT 0 COMMENT '安全水位',
total_in BIGINT NOT NULL DEFAULT 0 COMMENT '累计入池',
total_out BIGINT NOT NULL DEFAULT 0 COMMENT '累计 RTP 价值支出',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘基础奖池'`,
`CREATE TABLE IF NOT EXISTS wheel_draw_records (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
draw_id VARCHAR(128) NOT NULL COMMENT '抽奖 ID',
command_id VARCHAR(128) NOT NULL COMMENT '幂等命令 ID',
user_id BIGINT NOT NULL COMMENT '用户 ID',
device_id VARCHAR(128) NOT NULL COMMENT '设备 ID',
visible_region_id BIGINT NOT NULL DEFAULT 0 COMMENT '可见区域 ID',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
coin_spent BIGINT NOT NULL COMMENT '消耗金币',
rule_version BIGINT NOT NULL COMMENT '规则版本',
rtp_window_index BIGINT NOT NULL COMMENT 'RTP 窗口',
selected_tier_id VARCHAR(96) NOT NULL COMMENT '命中奖档',
reward_type VARCHAR(32) NOT NULL COMMENT 'coin/gift/prop/dress',
reward_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '礼物或道具资源 ID',
reward_count BIGINT NOT NULL DEFAULT 0 COMMENT '奖励数量',
reward_coins BIGINT NOT NULL DEFAULT 0 COMMENT '金币奖励金额',
rtp_value_coins BIGINT NOT NULL DEFAULT 0 COMMENT '计入 RTP 的奖励价值',
candidate_tiers_json JSON NOT NULL COMMENT '候选过滤快照',
rtp_snapshot_json JSON NOT NULL COMMENT 'RTP 快照',
prize_metadata_json JSON NOT NULL COMMENT '奖品扩展快照',
reward_status VARCHAR(32) NOT NULL COMMENT 'pending/granted/failed',
reward_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包或资产发放交易 ID',
reward_failure_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '发放失败原因',
paid_at_ms BIGINT NOT NULL COMMENT '扣费时间UTC epoch ms',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, draw_id),
UNIQUE KEY uk_wheel_draw_command (app_code, command_id),
KEY idx_wheel_draw_wheel (app_code, wheel_id, created_at_ms),
KEY idx_wheel_draw_user (app_code, user_id, created_at_ms),
KEY idx_wheel_draw_status (app_code, reward_status, updated_at_ms),
KEY idx_wheel_draw_created (app_code, created_at_ms, draw_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘抽奖事实'`,
`CREATE TABLE IF NOT EXISTS wheel_draw_stats (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
total_draws BIGINT NOT NULL DEFAULT 0 COMMENT '抽奖次数',
unique_users BIGINT NOT NULL DEFAULT 0 COMMENT '参与用户数',
total_spent_coins BIGINT NOT NULL DEFAULT 0 COMMENT '消耗金币',
total_rtp_value_coins BIGINT NOT NULL DEFAULT 0 COMMENT '计入 RTP 的返奖价值',
pending_draws BIGINT NOT NULL DEFAULT 0 COMMENT '待发放数量',
granted_draws BIGINT NOT NULL DEFAULT 0 COMMENT '已发放数量',
failed_draws BIGINT NOT NULL DEFAULT 0 COMMENT '发放失败数量',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘后台汇总统计'`,
`CREATE TABLE IF NOT EXISTS wheel_draw_stat_users (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
wheel_id VARCHAR(96) NOT NULL COMMENT '转盘 ID',
user_id BIGINT NOT NULL COMMENT '用户 ID',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
PRIMARY KEY (app_code, wheel_id, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转盘统计用户去重'`,
}
for _, statement := range statements {
if _, err := r.db.ExecContext(ctx, statement); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,216 @@
package grpc
import (
"context"
"net"
"testing"
"time"
grpcpkg "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
"google.golang.org/protobuf/proto"
activityv1 "hyapp.local/api/proto/activity/v1"
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
roomv1 "hyapp.local/api/proto/room/v1"
agencydomain "hyapp/services/activity-service/internal/domain/agencyopening"
agencyservice "hyapp/services/activity-service/internal/service/agencyopening"
broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
"hyapp/services/activity-service/internal/testutil/mysqltest"
)
func TestAgencyOpeningGRPCInterfaceFlow(t *testing.T) {
repository := mysqltest.NewRepository(t)
now := time.Now().UTC()
ownerUserID := int64(42001)
agencySvc := agencyservice.New(repository, nil, fakeAgencyOpeningFlowSource{
snapshot: agencydomain.AgencySnapshot{
AgencyID: 8001,
OwnerUserID: ownerUserID,
Name: "Window Agency",
HostCount: 12,
AgencyCreatedAtMS: now.Add(-time.Hour).UnixMilli(),
Status: "active",
},
}, &fakeAgencyOpeningFlowRoom{ownerUserID: ownerUserID})
conn := newAgencyOpeningFlowGRPCConn(t, agencySvc, repository)
adminClient := activityv1.NewAdminAgencyOpeningServiceClient(conn)
appClient := activityv1.NewAgencyOpeningServiceClient(conn)
roomEventClient := activityv1.NewRoomEventConsumerServiceClient(conn)
ctx := context.Background()
createdCycle, err := adminClient.CreateAgencyOpeningCycle(ctx, &activityv1.UpsertAgencyOpeningCycleRequest{
Meta: agencyOpeningFlowMeta("agency-opening-cycle-create"),
Cycle: &activityv1.AgencyOpeningCycle{
Title: "Agency Opening Interface Flow",
Status: "active",
StartMs: now.Add(-time.Minute).UnixMilli(),
EndMs: now.Add(48 * time.Hour).UnixMilli(),
MinHostCount: 10,
MaxAgencyAgeDays: 30,
Rewards: []*activityv1.AgencyOpeningReward{{
RankNo: 1,
ThresholdCoinSpent: 500,
RewardCoinAmount: 50,
}},
},
OperatorAdminId: 7,
})
if err != nil {
t.Fatalf("create agency opening cycle failed: %v", err)
}
if !createdCycle.GetCreated() || createdCycle.GetCycle().GetCycleId() == "" {
t.Fatalf("create cycle response mismatch: %+v", createdCycle)
}
beforeApply, err := appClient.GetAgencyOpeningStatus(ctx, &activityv1.GetAgencyOpeningStatusRequest{
Meta: agencyOpeningFlowMeta("agency-opening-status-before-apply"),
UserId: ownerUserID,
})
if err != nil {
t.Fatalf("get status before apply failed: %v", err)
}
if beforeApply.GetJoined() || !beforeApply.GetEligible() {
t.Fatalf("owner should be eligible before apply: %+v", beforeApply)
}
applied, err := appClient.ApplyAgencyOpening(ctx, &activityv1.ApplyAgencyOpeningRequest{
Meta: agencyOpeningFlowMeta("agency-opening-apply"),
UserId: ownerUserID,
CommandId: "cmd-agency-opening-flow-apply",
})
if err != nil {
t.Fatalf("apply agency opening failed: %v", err)
}
if !applied.GetCreated() || applied.GetApplication().GetStatus() != agencydomain.ApplicationStatusApplied || applied.GetApplication().GetScoreStartMs() != 0 {
t.Fatalf("apply response mismatch: %+v", applied)
}
consumeAgencyOpeningFlowGift(t, ctx, roomEventClient, "gift-before-approval", "room-flow-1", ownerUserID, now.UnixMilli(), 700)
approved, err := adminClient.ReviewAgencyOpeningApplication(ctx, &activityv1.ReviewAgencyOpeningApplicationRequest{
Meta: agencyOpeningFlowMeta("agency-opening-approve"),
ApplicationId: applied.GetApplication().GetApplicationId(),
Action: "approve",
OperatorAdminId: 7,
})
if err != nil {
t.Fatalf("approve agency opening failed: %v", err)
}
if approved.GetApplication().GetStatus() != agencydomain.ApplicationStatusApproved ||
approved.GetApplication().GetScoreStartMs() != approved.GetApplication().GetApprovedAtMs() ||
approved.GetApplication().GetScoreEndMs() <= approved.GetApplication().GetScoreStartMs() {
t.Fatalf("approve response should open a scoring window: %+v", approved)
}
insideWindowMS := approved.GetApplication().GetScoreStartMs() + int64(time.Second.Milliseconds())
consumeAgencyOpeningFlowGift(t, ctx, roomEventClient, "gift-inside-window", "room-flow-1", ownerUserID, insideWindowMS, 900)
afterInside, err := appClient.GetAgencyOpeningStatus(ctx, &activityv1.GetAgencyOpeningStatusRequest{
Meta: agencyOpeningFlowMeta("agency-opening-status-after-inside"),
UserId: ownerUserID,
})
if err != nil {
t.Fatalf("get status after inside-window gift failed: %v", err)
}
if afterInside.GetApplication().GetScoreCoinAmount() != 900 ||
afterInside.GetApplication().GetScoreEndMs() != approved.GetApplication().GetScoreEndMs() {
t.Fatalf("inside-window gift should be counted once: %+v", afterInside.GetApplication())
}
consumeAgencyOpeningFlowGift(t, ctx, roomEventClient, "gift-at-window-end", "room-flow-1", ownerUserID, approved.GetApplication().GetScoreEndMs(), 1200)
afterWindow, err := appClient.GetAgencyOpeningStatus(ctx, &activityv1.GetAgencyOpeningStatusRequest{
Meta: agencyOpeningFlowMeta("agency-opening-status-after-window"),
UserId: ownerUserID,
})
if err != nil {
t.Fatalf("get status after window-end gift failed: %v", err)
}
if afterWindow.GetApplication().GetScoreCoinAmount() != 900 {
t.Fatalf("window-end gift must not be counted: %+v", afterWindow.GetApplication())
}
}
func newAgencyOpeningFlowGRPCConn(t *testing.T, agencySvc *agencyservice.Service, repository *mysqltest.Repository) *grpcpkg.ClientConn {
t.Helper()
listener := bufconn.Listen(1024 * 1024)
server := grpcpkg.NewServer()
activityv1.RegisterAgencyOpeningServiceServer(server, NewAgencyOpeningServer(agencySvc))
activityv1.RegisterAdminAgencyOpeningServiceServer(server, NewAdminAgencyOpeningServer(agencySvc))
broadcastSvc := broadcastservice.New(broadcastservice.Config{SuperGiftMinValue: 1_000_000_000}, repository, nil, nil)
activityv1.RegisterRoomEventConsumerServiceServer(server, NewBroadcastServer(broadcastSvc, nil, nil, nil, nil, agencySvc))
go func() {
_ = server.Serve(listener)
}()
t.Cleanup(func() {
server.Stop()
_ = listener.Close()
})
conn, err := grpcpkg.NewClient("passthrough:///agency-opening-flow-bufconn",
grpcpkg.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return listener.Dial()
}),
grpcpkg.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
t.Fatalf("dial agency opening flow grpc failed: %v", err)
}
t.Cleanup(func() { _ = conn.Close() })
return conn
}
func consumeAgencyOpeningFlowGift(t *testing.T, ctx context.Context, client activityv1.RoomEventConsumerServiceClient, eventID string, roomID string, ownerUserID int64, occurredAtMS int64, giftValue int64) {
t.Helper()
body, err := proto.Marshal(&roomeventsv1.RoomGiftSent{
SenderUserId: 10001,
TargetUserId: ownerUserID,
GiftValue: giftValue,
CoinSpent: giftValue,
})
if err != nil {
t.Fatalf("marshal room gift event failed: %v", err)
}
if _, err := client.ConsumeRoomEvent(ctx, &activityv1.ConsumeRoomEventRequest{
Meta: agencyOpeningFlowMeta("consume-" + eventID),
Envelope: &roomeventsv1.EventEnvelope{
EventId: eventID,
RoomId: roomID,
EventType: "RoomGiftSent",
OccurredAtMs: occurredAtMS,
AppCode: "lalu",
Body: body,
},
}); err != nil {
t.Fatalf("consume room event %s failed: %v", eventID, err)
}
}
func agencyOpeningFlowMeta(requestID string) *activityv1.RequestMeta {
return &activityv1.RequestMeta{
RequestId: requestID,
Caller: "agency-opening-interface-flow-test",
AppCode: "lalu",
SentAtMs: time.Now().UTC().UnixMilli(),
}
}
type fakeAgencyOpeningFlowSource struct {
snapshot agencydomain.AgencySnapshot
}
func (f fakeAgencyOpeningFlowSource) ResolveOwnerAgency(context.Context, int64) (agencydomain.AgencySnapshot, bool, error) {
return f.snapshot, true, nil
}
type fakeAgencyOpeningFlowRoom struct {
ownerUserID int64
}
func (r *fakeAgencyOpeningFlowRoom) AdminGetRoom(context.Context, *roomv1.AdminGetRoomRequest, ...grpcpkg.CallOption) (*roomv1.AdminGetRoomResponse, error) {
return &roomv1.AdminGetRoomResponse{Room: &roomv1.AdminRoomListItem{OwnerUserId: r.ownerUserID}}, nil
}

View File

@ -122,6 +122,18 @@ func (s *AdminAgencyOpeningServer) ListAgencyOpeningApplications(ctx context.Con
return agencyOpeningApplicationsResponse(items, total), nil
}
func (s *AdminAgencyOpeningServer) ReviewAgencyOpeningApplication(ctx context.Context, req *activityv1.ReviewAgencyOpeningApplicationRequest) (*activityv1.ReviewAgencyOpeningApplicationResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
if req.GetAction() != "approve" {
return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "review action is invalid"))
}
item, err := s.svc.ApproveApplication(ctx, req.GetApplicationId(), req.GetOperatorAdminId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.ReviewAgencyOpeningApplicationResponse{Application: agencyOpeningApplicationToProto(item)}, nil
}
func agencyOpeningCycleFromProto(item *activityv1.AgencyOpeningCycle) domain.Cycle {
if item == nil {
return domain.Cycle{}
@ -200,6 +212,10 @@ func agencyOpeningApplicationToProto(item domain.Application) *activityv1.Agency
WalletTransactionId: item.WalletTransactionID,
FailureReason: item.FailureReason,
AppliedAtMs: item.AppliedAtMS,
ApprovedAtMs: item.ApprovedAtMS,
ScoreStartMs: item.ScoreStartMS,
ScoreEndMs: item.ScoreEndMS,
ReviewedByAdminId: item.ReviewedByAdminID,
GrantedAtMs: item.GrantedAtMS,
UpdatedAtMs: item.UpdatedAtMS,
}

View File

@ -0,0 +1,212 @@
package grpc
import (
"context"
activityv1 "hyapp.local/api/proto/activity/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/wheel"
service "hyapp/services/activity-service/internal/service/wheel"
)
type WheelServer struct {
activityv1.UnimplementedWheelServiceServer
svc *service.Service
}
func NewWheelServer(svc *service.Service) *WheelServer {
return &WheelServer{svc: svc}
}
func (s *WheelServer) ExecuteWheelDraw(ctx context.Context, req *activityv1.ExecuteWheelDrawRequest) (*activityv1.ExecuteWheelDrawResponse, error) {
meta := req.GetWheel()
ctx = appcode.WithContext(ctx, meta.GetMeta().GetAppCode())
result, err := s.svc.Draw(ctx, domain.DrawCommand{
CommandID: meta.GetCommandId(),
WheelID: meta.GetWheelId(),
UserID: meta.GetUserId(),
DeviceID: meta.GetDeviceId(),
DrawCount: meta.GetDrawCount(),
CoinSpent: meta.GetCoinSpent(),
PaidAtMS: meta.GetPaidAtMs(),
VisibleRegionID: meta.GetVisibleRegionId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.ExecuteWheelDrawResponse{Result: wheelDrawResultToProto(result)}, nil
}
type AdminWheelServer struct {
activityv1.UnimplementedAdminWheelServiceServer
svc *service.Service
}
func NewAdminWheelServer(svc *service.Service) *AdminWheelServer {
return &AdminWheelServer{svc: svc}
}
func (s *AdminWheelServer) GetWheelConfig(ctx context.Context, req *activityv1.GetWheelConfigRequest) (*activityv1.GetWheelConfigResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
config, err := s.svc.GetConfig(ctx, req.GetWheelId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.GetWheelConfigResponse{Config: wheelRuleConfigToProto(config)}, nil
}
func (s *AdminWheelServer) UpsertWheelConfig(ctx context.Context, req *activityv1.UpsertWheelConfigRequest) (*activityv1.UpsertWheelConfigResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
config := wheelRuleConfigFromProto(req.GetConfig())
config.CreatedByAdminID = req.GetOperatorAdminId()
updated, err := s.svc.UpsertConfig(ctx, config)
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.UpsertWheelConfigResponse{Config: wheelRuleConfigToProto(updated)}, nil
}
func (s *AdminWheelServer) ListWheelDraws(ctx context.Context, req *activityv1.ListWheelDrawsRequest) (*activityv1.ListWheelDrawsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
draws, total, err := s.svc.ListDraws(ctx, domain.DrawQuery{
WheelID: req.GetWheelId(),
UserID: req.GetUserId(),
Status: req.GetStatus(),
Page: req.GetPage(),
PageSize: req.GetPageSize(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &activityv1.ListWheelDrawsResponse{Draws: make([]*activityv1.WheelDrawResult, 0, len(draws)), Total: total}
for _, draw := range draws {
resp.Draws = append(resp.Draws, wheelDrawResultToProto(draw))
}
return resp, nil
}
func (s *AdminWheelServer) GetWheelDrawSummary(ctx context.Context, req *activityv1.GetWheelDrawSummaryRequest) (*activityv1.GetWheelDrawSummaryResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
summary, err := s.svc.GetDrawSummary(ctx, domain.DrawQuery{
WheelID: req.GetWheelId(),
UserID: req.GetUserId(),
Status: req.GetStatus(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.GetWheelDrawSummaryResponse{Summary: wheelDrawSummaryToProto(summary)}, nil
}
func wheelRuleConfigFromProto(config *activityv1.WheelRuleConfig) domain.RuleConfig {
if config == nil {
return domain.RuleConfig{}
}
tiers := make([]domain.Tier, 0, len(config.GetTiers()))
for _, tier := range config.GetTiers() {
tiers = append(tiers, domain.Tier{
TierID: tier.GetTierId(),
DisplayName: tier.GetDisplayName(),
RewardType: tier.GetRewardType(),
RewardID: tier.GetRewardId(),
RewardCount: tier.GetRewardCount(),
RewardCoins: tier.GetRewardCoins(),
RTPValueCoins: tier.GetRtpValueCoins(),
WeightPPM: tier.GetWeightPpm(),
Enabled: tier.GetEnabled(),
MetadataJSON: tier.GetMetadataJson(),
})
}
return domain.RuleConfig{
AppCode: config.GetAppCode(),
WheelID: config.GetWheelId(),
RuleVersion: config.GetRuleVersion(),
Enabled: config.GetEnabled(),
DrawPriceCoins: config.GetDrawPriceCoins(),
TargetRTPPPM: config.GetTargetRtpPpm(),
PoolRatePPM: config.GetPoolRatePpm(),
SettlementWindowDraws: config.GetSettlementWindowDraws(),
InitialPoolCoins: config.GetInitialPoolCoins(),
PoolReserveCoins: config.GetPoolReserveCoins(),
MaxSingleRTPPayout: config.GetMaxSingleRtpPayout(),
EffectiveFromMS: config.GetEffectiveFromMs(),
CreatedByAdminID: config.GetCreatedByAdminId(),
CreatedAtMS: config.GetCreatedAtMs(),
Tiers: tiers,
}
}
func wheelRuleConfigToProto(config domain.RuleConfig) *activityv1.WheelRuleConfig {
tiers := make([]*activityv1.WheelPrizeTier, 0, len(config.Tiers))
for _, tier := range config.Tiers {
tiers = append(tiers, &activityv1.WheelPrizeTier{
TierId: tier.TierID,
DisplayName: tier.DisplayName,
RewardType: tier.RewardType,
RewardId: tier.RewardID,
RewardCount: tier.RewardCount,
RewardCoins: tier.RewardCoins,
RtpValueCoins: tier.RTPValueCoins,
WeightPpm: tier.WeightPPM,
Enabled: tier.Enabled,
MetadataJson: tier.MetadataJSON,
})
}
return &activityv1.WheelRuleConfig{
AppCode: config.AppCode,
WheelId: config.WheelID,
RuleVersion: config.RuleVersion,
Enabled: config.Enabled,
DrawPriceCoins: config.DrawPriceCoins,
TargetRtpPpm: config.TargetRTPPPM,
PoolRatePpm: config.PoolRatePPM,
SettlementWindowDraws: config.SettlementWindowDraws,
InitialPoolCoins: config.InitialPoolCoins,
PoolReserveCoins: config.PoolReserveCoins,
MaxSingleRtpPayout: config.MaxSingleRTPPayout,
EffectiveFromMs: config.EffectiveFromMS,
CreatedByAdminId: config.CreatedByAdminID,
CreatedAtMs: config.CreatedAtMS,
Tiers: tiers,
}
}
func wheelDrawResultToProto(result domain.DrawResult) *activityv1.WheelDrawResult {
return &activityv1.WheelDrawResult{
DrawId: result.DrawID,
DrawIds: result.DrawIDs,
CommandId: result.CommandID,
WheelId: result.WheelID,
RuleVersion: result.RuleVersion,
SelectedTierId: result.SelectedTierID,
RewardType: result.RewardType,
RewardId: result.RewardID,
RewardCount: result.RewardCount,
RewardCoins: result.RewardCoins,
RtpValueCoins: result.RTPValueCoins,
RewardStatus: result.RewardStatus,
RtpWindowIndex: result.RTPWindowIndex,
ActualRtpPpm: result.ActualRTPPPM,
CreatedAtMs: result.CreatedAtMS,
WalletTransactionId: result.WalletTransactionID,
CoinBalanceAfter: result.CoinBalanceAfter,
MetadataJson: result.MetadataJSON,
}
}
func wheelDrawSummaryToProto(summary domain.DrawSummary) *activityv1.WheelDrawSummary {
return &activityv1.WheelDrawSummary{
WheelId: summary.WheelID,
TotalDraws: summary.TotalDraws,
UniqueUsers: summary.UniqueUsers,
TotalSpentCoins: summary.TotalSpentCoins,
TotalRtpValueCoins: summary.TotalRTPValueCoins,
ActualRtpPpm: summary.ActualRTPPPM,
PendingDraws: summary.PendingDraws,
GrantedDraws: summary.GrantedDraws,
FailedDraws: summary.FailedDraws,
}
}

View File

@ -12,9 +12,9 @@ launch_session_ttl: "15m"
tencent_im:
# Docker/testbox 联调房内猜拳 PK 房间群消息;必须和 gateway/room 使用同一个 IM 应用。
enabled: true
sdk_app_id: 20036101
secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c"
admin_identifier: "900100100"
sdk_app_id: 20040101
secret_key: "2f6a5ec58cccf79647557a96c15e6efe89ba5a44c474096bde2589a633fcd45e"
admin_identifier: "administrator"
admin_user_sig_ttl: "24h"
endpoint: "adminapisgp.im.qcloud.com"
group_type: "ChatRoom"

View File

@ -12,9 +12,9 @@ launch_session_ttl: "15m"
tencent_im:
# 本地联调房内猜拳 PK 房间群消息;必须和 gateway/room 使用同一个 IM 应用。
enabled: true
sdk_app_id: 20036101
secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c"
admin_identifier: "900100100"
sdk_app_id: 20040101
secret_key: "2f6a5ec58cccf79647557a96c15e6efe89ba5a44c474096bde2589a633fcd45e"
admin_identifier: "administrator"
admin_user_sig_ttl: "24h"
endpoint: "adminapisgp.im.qcloud.com"
group_type: "ChatRoom"

View File

@ -51,6 +51,12 @@ grpc_client:
app_config:
# App 运行时配置由后台 APP配置写入 hyapp_admingateway 只读下发给 App。
mysql_dsn: "hyapp:hyapp@tcp(mysql:3306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC"
# 房间白名单等低频配置用 Redis 缓存 5 分钟,避免每次列表请求查后台库。
redis_addr: "redis:6379"
redis_password: ""
redis_db: 0
key_prefix: "gateway:app_config:"
cache_ttl: "5m"
leaderboard:
# 活动用户榜单只读 wallet_transactions 的送礼事实;时间窗口统一按 UTC 计算。
wallet_mysql_dsn: "hyapp:hyapp@tcp(mysql:3306)/hyapp_wallet?parseTime=true&charset=utf8mb4&loc=UTC"
@ -81,9 +87,9 @@ login_risk:
blocked_timezone_prefixes: []
tencent_im:
# Docker 本地联调腾讯云 IM UserSig必须与 room-service 使用同一组应用配置。
sdk_app_id: 20036101
secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c"
admin_identifier: "900100100"
sdk_app_id: 20040101
secret_key: "2f6a5ec58cccf79647557a96c15e6efe89ba5a44c474096bde2589a633fcd45e"
admin_identifier: "administrator"
admin_user_sig_ttl: "24h"
user_sig_ttl: "24h"
endpoint: "adminapisgp.im.qcloud.com"

View File

@ -55,6 +55,12 @@ grpc_client:
app_config:
# App 运行时配置由后台 APP配置写入 hyapp_admingateway 只读下发给 App。
mysql_dsn: "TENCENT_MYSQL_USER:TENCENT_MYSQL_PASSWORD@tcp(TENCENT_MYSQL_HOST:3306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC"
# 房间白名单等低频配置用 Redis 缓存 5 分钟,避免每次列表请求查后台库。
redis_addr: "TENCENT_REDIS_HOST:6379"
redis_password: "TENCENT_REDIS_PASSWORD"
redis_db: 0
key_prefix: "gateway:app_config:"
cache_ttl: "5m"
leaderboard:
# 活动用户榜单只读钱包送礼事实;线上建议配置只读账号。
wallet_mysql_dsn: "TENCENT_MYSQL_USER:TENCENT_MYSQL_PASSWORD@tcp(TENCENT_MYSQL_HOST:3306)/hyapp_wallet?parseTime=true&charset=utf8mb4&loc=UTC"

View File

@ -51,6 +51,12 @@ grpc_client:
app_config:
# App 运行时配置由后台 APP配置写入 hyapp_admingateway 只读下发给 App。
mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC"
# 房间白名单等低频配置用 Redis 缓存 5 分钟,避免每次列表请求查后台库。
redis_addr: "127.0.0.1:13379"
redis_password: ""
redis_db: 0
key_prefix: "gateway:app_config:"
cache_ttl: "5m"
leaderboard:
# 活动用户榜单只读 wallet_transactions 的送礼事实;时间窗口统一按 UTC 计算。
wallet_mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_wallet?parseTime=true&charset=utf8mb4&loc=UTC"
@ -81,9 +87,9 @@ login_risk:
blocked_timezone_prefixes: []
tencent_im:
# 本地联调腾讯云 IM UserSigSDKAppID 和密钥必须与 room-service 保持一致。
sdk_app_id: 20036101
secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c"
admin_identifier: "900100100"
sdk_app_id: 20040101
secret_key: "2f6a5ec58cccf79647557a96c15e6efe89ba5a44c474096bde2589a633fcd45e"
admin_identifier: "administrator"
admin_user_sig_ttl: "24h"
# 客户端 UserSig 有效期;线上建议 12h-7d按登录刷新策略决定。
user_sig_ttl: "24h"

View File

@ -110,6 +110,7 @@ func New(cfg config.Config) (*App, error) {
var weeklyStarClient client.WeeklyStarClient = client.NewGRPCWeeklyStarClient(activityConn)
var cpWeeklyRankClient client.CPWeeklyRankClient = client.NewGRPCCPWeeklyRankClient(activityConn)
var agencyOpeningClient client.AgencyOpeningClient = client.NewGRPCAgencyOpeningClient(activityConn)
var wheelClient client.WheelClient = client.NewGRPCWheelClient(activityConn)
var broadcastClient client.BroadcastClient = client.NewGRPCBroadcastClient(activityConn)
var gameClient client.GameClient = client.NewGRPCGameClient(gameConn)
var statisticsClient client.StatisticsClient = client.NewHTTPStatisticsClient(cfg.Statistics.BaseURL, cfg.Statistics.Timeout)
@ -184,6 +185,7 @@ func New(cfg config.Config) (*App, error) {
handler.SetWeeklyStarClient(weeklyStarClient)
handler.SetCPWeeklyRankClient(cpWeeklyRankClient)
handler.SetAgencyOpeningClient(agencyOpeningClient)
handler.SetWheelClient(wheelClient)
handler.SetBroadcastClient(broadcastClient)
handler.SetGameClient(gameClient)
handler.SetStatisticsClient(statisticsClient)
@ -345,7 +347,22 @@ func openAppConfigReader(cfg config.AppConfigConfig) (*appconfig.MySQLReader, er
return nil, nil
}
return appconfig.OpenMySQLReader(cfg.MySQLDSN)
reader, err := appconfig.OpenMySQLReader(cfg.MySQLDSN)
if err != nil {
return nil, err
}
if strings.TrimSpace(cfg.RedisAddr) == "" {
return reader, nil
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cacheClient, err := appconfig.OpenRedisCache(ctx, cfg.RedisAddr, cfg.RedisPassword, cfg.RedisDB)
if err != nil {
_ = reader.Close()
return nil, fmt.Errorf("connect app config redis: %w", err)
}
reader.SetRedisCache(cacheClient, cfg.KeyPrefix, cfg.CacheTTL)
return reader, nil
}
func openLeaderboardWalletDB(cfg config.LeaderboardConfig) (*sql.DB, error) {

View File

@ -4,17 +4,21 @@ package appconfig
import (
"context"
"database/sql"
"encoding/json"
"errors"
"strconv"
"strings"
"time"
mysqlDriver "github.com/go-sql-driver/mysql"
"github.com/redis/go-redis/v9"
)
const h5LinkGroup = "h5-links"
const roomRegionWhitelistGroup = "room-region-whitelist"
const listH5LinksSQL = "SELECT `key`, COALESCE(description, ''), COALESCE(value, ''), updated_at_ms FROM admin_app_configs WHERE `group` = ? ORDER BY `key` ASC"
const getRoomRegionWhitelistSQL = "SELECT COALESCE(value, '') FROM admin_app_configs WHERE `group` = ? AND `key` = ? LIMIT 1"
const bdLeaderPositionAliasSQL = `
SELECT position_alias
FROM admin_bd_leader_position_aliases
@ -185,11 +189,15 @@ type Reader interface {
ListSplashScreens(ctx context.Context, query SplashScreenQuery) ([]SplashScreen, error)
ListPopups(ctx context.Context, query PopupQuery) ([]Popup, error)
LatestVersion(ctx context.Context, query VersionQuery) (Version, error)
RoomRegionWhitelistAllows(ctx context.Context, appCode string, userID int64) (bool, error)
}
// MySQLReader 从 hyapp_admin 读取后台 App 配置。
type MySQLReader struct {
db *sql.DB
db *sql.DB
cache *redis.Client
cachePrefix string
cacheTTL time.Duration
}
// OpenMySQLReader 创建后台配置只读连接池。
@ -208,7 +216,44 @@ func (r *MySQLReader) Close() error {
return nil
}
return r.db.Close()
return errors.Join(r.db.Close(), r.closeCache())
}
func (r *MySQLReader) closeCache() error {
if r == nil || r.cache == nil {
return nil
}
return r.cache.Close()
}
// OpenRedisCache 创建 App 配置 Redis 缓存连接;启动期探测失败直接暴露配置问题。
func OpenRedisCache(ctx context.Context, addr string, password string, db int) (*redis.Client, error) {
client := redis.NewClient(&redis.Options{
Addr: strings.TrimSpace(addr),
Password: password,
DB: db,
})
if err := client.Ping(ctx).Err(); err != nil {
_ = client.Close()
return nil, err
}
return client, nil
}
// SetRedisCache 给低频后台配置挂 Redis 五分钟读缓存,避免房间列表每次都查 hyapp_admin。
func (r *MySQLReader) SetRedisCache(client *redis.Client, keyPrefix string, ttl time.Duration) {
if r == nil {
return
}
r.cache = client
r.cachePrefix = strings.TrimSpace(keyPrefix)
if r.cachePrefix == "" {
r.cachePrefix = "gateway:app_config:"
}
if ttl <= 0 {
ttl = 5 * time.Minute
}
r.cacheTTL = ttl
}
// ListH5Links 返回后台动态维护的 H5 入口集合。
@ -466,6 +511,102 @@ func (r *MySQLReader) LatestVersion(ctx context.Context, query VersionQuery) (Ve
return item, nil
}
// RoomRegionWhitelistAllows 判断用户是否可绕过房间区域限制;结果按 app+user 缓存五分钟。
func (r *MySQLReader) RoomRegionWhitelistAllows(ctx context.Context, appCode string, userID int64) (bool, error) {
if userID <= 0 {
return false, nil
}
if r == nil || r.db == nil {
return false, errors.New("app config reader is not configured")
}
app := normalizeAppCode(appCode)
if cached, ok, err := r.cachedRoomRegionWhitelist(ctx, app, userID); err == nil && ok {
return cached, nil
}
allowed, err := r.roomRegionWhitelistAllowsFromDB(ctx, app, userID)
if err != nil {
return false, err
}
r.storeRoomRegionWhitelistCache(ctx, app, userID, allowed)
return allowed, nil
}
func (r *MySQLReader) roomRegionWhitelistAllowsFromDB(ctx context.Context, appCode string, userID int64) (bool, error) {
var raw string
err := r.db.QueryRowContext(ctx, getRoomRegionWhitelistSQL, roomRegionWhitelistGroup, appCode).Scan(&raw)
if errors.Is(err, sql.ErrNoRows) || isMissingTableError(err) {
return false, nil
}
if err != nil {
return false, err
}
ids, err := parseRoomRegionWhitelist(raw)
if err != nil {
return false, err
}
_, ok := ids[userID]
return ok, nil
}
func (r *MySQLReader) cachedRoomRegionWhitelist(ctx context.Context, appCode string, userID int64) (bool, bool, error) {
if r == nil || r.cache == nil {
return false, false, nil
}
value, err := r.cache.Get(ctx, r.roomRegionWhitelistCacheKey(appCode, userID)).Result()
if errors.Is(err, redis.Nil) {
return false, false, nil
}
if err != nil {
return false, false, err
}
return value == "1", true, nil
}
func (r *MySQLReader) storeRoomRegionWhitelistCache(ctx context.Context, appCode string, userID int64, allowed bool) {
if r == nil || r.cache == nil {
return
}
value := "0"
if allowed {
value = "1"
}
// 缓存只优化读路径;写入失败时保持 DB 判定结果,不让 Redis 抖动阻断房间列表。
_ = r.cache.Set(ctx, r.roomRegionWhitelistCacheKey(appCode, userID), value, r.cacheTTL).Err()
}
func (r *MySQLReader) roomRegionWhitelistCacheKey(appCode string, userID int64) string {
return strings.TrimRight(r.cachePrefix, ":") + ":room_region_whitelist:" + normalizeAppCode(appCode) + ":" + strconv.FormatInt(userID, 10)
}
type roomRegionWhitelistPayload struct {
UserIDs []string `json:"user_ids"`
}
func parseRoomRegionWhitelist(raw string) (map[int64]struct{}, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return map[int64]struct{}{}, nil
}
var payload roomRegionWhitelistPayload
if strings.HasPrefix(raw, "[") {
if err := json.Unmarshal([]byte(raw), &payload.UserIDs); err != nil {
return nil, err
}
} else if err := json.Unmarshal([]byte(raw), &payload); err != nil {
return nil, err
}
out := make(map[int64]struct{}, len(payload.UserIDs))
for _, item := range payload.UserIDs {
id, err := strconv.ParseInt(strings.TrimSpace(item), 10, 64)
if err != nil || id <= 0 {
continue
}
out[id] = struct{}{}
}
return out, nil
}
// StaticReader 给测试和临时环境提供内存版 H5 配置读取。
type StaticReader struct {
Links []H5Link
@ -476,6 +617,7 @@ type StaticReader struct {
Version Version
Err error
PositionAliases map[string]string
RoomWhitelist map[string]bool
}
// ListH5Links 返回预置 H5 配置。
@ -556,6 +698,14 @@ func (r StaticReader) LatestVersion(_ context.Context, query VersionQuery) (Vers
return item, nil
}
// RoomRegionWhitelistAllows 返回内存白名单结果,测试可用 "app:user_id" 作为 key。
func (r StaticReader) RoomRegionWhitelistAllows(_ context.Context, appCode string, userID int64) (bool, error) {
if r.Err != nil {
return false, r.Err
}
return r.RoomWhitelist[bdLeaderPositionAliasKey(appCode, userID)], nil
}
func normalizeAppCode(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
if value == "" {
@ -606,13 +756,15 @@ func NormalizeBannerDisplayScope(value string) string {
return "room"
case "recharge", "充值页":
return "recharge"
case "me", "我的页", "我的":
return "me"
default:
return ""
}
}
func bannerDisplayScopeList(value string) []string {
seen := make(map[string]struct{}, 3)
seen := make(map[string]struct{}, 4)
for _, part := range strings.Split(value, ",") {
scope := NormalizeBannerDisplayScope(part)
if scope != "" {
@ -620,7 +772,7 @@ func bannerDisplayScopeList(value string) []string {
}
}
out := make([]string, 0, len(seen))
for _, scope := range []string{"home", "room", "recharge"} {
for _, scope := range []string{"home", "room", "recharge", "me"} {
if _, ok := seen[scope]; ok {
out = append(out, scope)
}

View File

@ -8,6 +8,8 @@ func TestNormalizeBannerDisplayScopeAcceptsChineseLabels(t *testing.T) {
"首页": "home",
"房间内": "room",
"充值页": "recharge",
"我的页": "me",
"ME": "me",
"ROOM": "room",
"bad": "",
}
@ -19,8 +21,8 @@ func TestNormalizeBannerDisplayScopeAcceptsChineseLabels(t *testing.T) {
}
func TestBannerDisplayScopeListParsesMultiScopeValue(t *testing.T) {
got := bannerDisplayScopeList("room,home,recharge")
want := []string{"home", "room", "recharge"}
got := bannerDisplayScopeList("room,home,recharge,me")
want := []string{"home", "room", "recharge", "me"}
if len(got) != len(want) {
t.Fatalf("scope count mismatch: got %+v want %+v", got, want)
}
@ -30,3 +32,25 @@ func TestBannerDisplayScopeListParsesMultiScopeValue(t *testing.T) {
}
}
}
func TestParseRoomRegionWhitelistSupportsObjectAndArrayPayloads(t *testing.T) {
cases := []string{
`{"user_ids":["325379237278126080","1001","1001","0","bad"]}`,
`["325379237278126080","1001","1001","0","bad"]`,
}
for _, input := range cases {
got, err := parseRoomRegionWhitelist(input)
if err != nil {
t.Fatalf("parse whitelist %s failed: %v", input, err)
}
if _, ok := got[325379237278126080]; !ok {
t.Fatalf("long user id missing: %+v", got)
}
if _, ok := got[1001]; !ok {
t.Fatalf("small user id missing: %+v", got)
}
if _, ok := got[0]; ok {
t.Fatalf("zero user id must be ignored: %+v", got)
}
}
}

View File

@ -29,6 +29,7 @@ type WalletClient interface {
GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error)
PurchaseVip(ctx context.Context, req *walletv1.PurchaseVipRequest) (*walletv1.PurchaseVipResponse, error)
DebitCPBreakupFee(ctx context.Context, req *walletv1.DebitCPBreakupFeeRequest) (*walletv1.DebitCPBreakupFeeResponse, error)
DebitWheelDraw(ctx context.Context, req *walletv1.DebitWheelDrawRequest) (*walletv1.DebitWheelDrawResponse, error)
GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error)
TransferCoinFromSeller(ctx context.Context, req *walletv1.TransferCoinFromSellerRequest) (*walletv1.TransferCoinFromSellerResponse, error)
ListCoinSellerSalaryExchangeRateTiers(ctx context.Context, req *walletv1.ListCoinSellerSalaryExchangeRateTiersRequest) (*walletv1.ListCoinSellerSalaryExchangeRateTiersResponse, error)
@ -138,6 +139,10 @@ func (c *grpcWalletClient) DebitCPBreakupFee(ctx context.Context, req *walletv1.
return c.client.DebitCPBreakupFee(ctx, req)
}
func (c *grpcWalletClient) DebitWheelDraw(ctx context.Context, req *walletv1.DebitWheelDrawRequest) (*walletv1.DebitWheelDrawResponse, error) {
return c.client.DebitWheelDraw(ctx, req)
}
func (c *grpcWalletClient) GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) {
return c.client.GrantVip(ctx, req)
}

View File

@ -0,0 +1,40 @@
package client
import (
"context"
activityv1 "hyapp.local/api/proto/activity/v1"
"google.golang.org/grpc"
)
// WheelClient abstracts gateway access to activity-service wheel config and draw execution.
type WheelClient interface {
ExecuteWheelDraw(ctx context.Context, req *activityv1.ExecuteWheelDrawRequest) (*activityv1.ExecuteWheelDrawResponse, error)
GetWheelConfig(ctx context.Context, req *activityv1.GetWheelConfigRequest) (*activityv1.GetWheelConfigResponse, error)
ListWheelDraws(ctx context.Context, req *activityv1.ListWheelDrawsRequest) (*activityv1.ListWheelDrawsResponse, error)
}
type grpcWheelClient struct {
wheel activityv1.WheelServiceClient
admin activityv1.AdminWheelServiceClient
}
func NewGRPCWheelClient(conn *grpc.ClientConn) WheelClient {
return &grpcWheelClient{
wheel: activityv1.NewWheelServiceClient(conn),
admin: activityv1.NewAdminWheelServiceClient(conn),
}
}
func (c *grpcWheelClient) ExecuteWheelDraw(ctx context.Context, req *activityv1.ExecuteWheelDrawRequest) (*activityv1.ExecuteWheelDrawResponse, error) {
return c.wheel.ExecuteWheelDraw(ctx, req)
}
func (c *grpcWheelClient) GetWheelConfig(ctx context.Context, req *activityv1.GetWheelConfigRequest) (*activityv1.GetWheelConfigResponse, error) {
return c.admin.GetWheelConfig(ctx, req)
}
func (c *grpcWheelClient) ListWheelDraws(ctx context.Context, req *activityv1.ListWheelDrawsRequest) (*activityv1.ListWheelDrawsResponse, error) {
return c.admin.ListWheelDraws(ctx, req)
}

View File

@ -115,6 +115,16 @@ type LoginRiskFastGuardConfig struct {
type AppConfigConfig struct {
// MySQLDSN 指向后台管理库 hyapp_admingateway 只读 App 运行时配置表。
MySQLDSN string `yaml:"mysql_dsn"`
// RedisAddr 用于缓存低频后台配置判定,默认复用 auth_rate_limit Redis。
RedisAddr string `yaml:"redis_addr"`
// RedisPassword 是 Redis 鉴权密码,默认复用 auth_rate_limit Redis。
RedisPassword string `yaml:"redis_password"`
// RedisDB 是 Redis logical DB默认复用 auth_rate_limit Redis。
RedisDB int `yaml:"redis_db"`
// KeyPrefix 隔离 App 配置缓存 key避免和登录风控、频控共用命名空间。
KeyPrefix string `yaml:"key_prefix"`
// CacheTTL 控制后台配置读缓存时间;房间白名单默认五分钟。
CacheTTL time.Duration `yaml:"cache_ttl"`
}
// LeaderboardConfig 描述 App 活动榜单读取钱包送礼事实的只读数据源。
@ -296,7 +306,10 @@ func Default() Config {
RetryableStatusCodes: []string{"UNAVAILABLE"},
},
AppConfig: AppConfigConfig{
MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC",
MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC",
RedisAddr: "127.0.0.1:13379",
KeyPrefix: "gateway:app_config:",
CacheTTL: 5 * time.Minute,
},
Leaderboard: LeaderboardConfig{
WalletMySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_wallet?parseTime=true&charset=utf8mb4&loc=UTC",
@ -424,6 +437,19 @@ func (cfg *Config) Normalize() error {
RetryableStatusCodes: runtimeGRPC.RetryableStatusCodes,
}
cfg.AppConfig.MySQLDSN = strings.TrimSpace(cfg.AppConfig.MySQLDSN)
cfg.AppConfig.RedisAddr = strings.TrimSpace(cfg.AppConfig.RedisAddr)
cfg.AppConfig.KeyPrefix = strings.TrimSpace(cfg.AppConfig.KeyPrefix)
if cfg.AppConfig.RedisAddr == "" {
cfg.AppConfig.RedisAddr = strings.TrimSpace(cfg.AuthRateLimit.RedisAddr)
cfg.AppConfig.RedisPassword = cfg.AuthRateLimit.RedisPassword
cfg.AppConfig.RedisDB = cfg.AuthRateLimit.RedisDB
}
if cfg.AppConfig.KeyPrefix == "" {
cfg.AppConfig.KeyPrefix = "gateway:app_config:"
}
if cfg.AppConfig.CacheTTL <= 0 {
cfg.AppConfig.CacheTTL = 5 * time.Minute
}
cfg.Leaderboard.WalletMySQLDSN = strings.TrimSpace(cfg.Leaderboard.WalletMySQLDSN)
cfg.LoginRisk.RedisAddr = strings.TrimSpace(cfg.LoginRisk.RedisAddr)
cfg.LoginRisk.KeyPrefix = strings.TrimSpace(cfg.LoginRisk.KeyPrefix)

View File

@ -65,6 +65,7 @@ type agencyOpeningRecordData struct {
CreatedAt string `json:"created_at,omitempty"`
OpeningRevenue string `json:"opening_revenue,omitempty"`
Reward int64 `json:"reward"`
ScoreEndMS int64 `json:"score_end_ms,omitempty"`
}
type agencyOpeningApplicationData struct {
@ -84,6 +85,10 @@ type agencyOpeningApplicationData struct {
WalletTransactionID string `json:"wallet_transaction_id,omitempty"`
FailureReason string `json:"failure_reason,omitempty"`
AppliedAtMS int64 `json:"applied_at_ms"`
ApprovedAtMS int64 `json:"approved_at_ms,omitempty"`
ScoreStartMS int64 `json:"score_start_ms,omitempty"`
ScoreEndMS int64 `json:"score_end_ms,omitempty"`
ReviewedByAdminID int64 `json:"reviewed_by_admin_id,omitempty"`
GrantedAtMS int64 `json:"granted_at_ms,omitempty"`
UpdatedAtMS int64 `json:"updated_at_ms,omitempty"`
}
@ -221,11 +226,8 @@ func agencyOpeningQualificationFromProto(status *activityv1.GetAgencyOpeningStat
agencyName = application.GetAgencyName()
}
}
openingStatus := "Not Joined"
if status.GetJoined() {
openingStatus = "Joined"
}
if !status.GetEnabled() {
openingStatus := agencyOpeningStatusLabel(application)
if !status.GetEnabled() && !status.GetJoined() {
openingStatus = "Disabled"
}
return agencyOpeningQualificationData{
@ -266,6 +268,7 @@ func agencyOpeningRecordFromProto(application *activityv1.AgencyOpeningApplicati
CreatedAt: agencyOpeningDate(application.GetAgencyCreatedAtMs()),
OpeningRevenue: fmt.Sprintf("%d", application.GetScoreCoinAmount()),
Reward: application.GetRewardCoinAmount(),
ScoreEndMS: application.GetScoreEndMs(),
}
}
@ -298,11 +301,37 @@ func agencyOpeningApplicationFromProto(item *activityv1.AgencyOpeningApplication
WalletTransactionID: item.GetWalletTransactionId(),
FailureReason: item.GetFailureReason(),
AppliedAtMS: item.GetAppliedAtMs(),
ApprovedAtMS: item.GetApprovedAtMs(),
ScoreStartMS: item.GetScoreStartMs(),
ScoreEndMS: item.GetScoreEndMs(),
ReviewedByAdminID: item.GetReviewedByAdminId(),
GrantedAtMS: item.GetGrantedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
}
func agencyOpeningStatusLabel(application *activityv1.AgencyOpeningApplication) string {
if application == nil {
return "Not Joined"
}
switch application.GetStatus() {
case "applied":
return "Pending Review"
case "approved":
return "Approved"
case "pending":
return "Pending Reward"
case "granted":
return "Rewarded"
case "failed":
return "Reward Failed"
case "expired":
return "Expired"
default:
return "Joined"
}
}
func formatAgencyOpeningCoinLabel(value int64) string {
if value <= 0 {
return "0"
@ -322,7 +351,8 @@ func agencyOpeningRules(cycle *activityv1.AgencyOpeningCycle) []string {
}
return []string{
fmt.Sprintf("Agency must have more than %d hosts before applying.", cycle.GetMinHostCount()),
"Opening revenue comes from the applicant user's room gift COIN revenue during the configured period.",
"Opening revenue starts from admin approval and only counts the applicant user's room gift COIN revenue within the next 24 hours.",
"If the 24-hour scoring window passes the activity end time, the activity end time is used as the cutoff.",
"Reward tier is matched by opening revenue after the period ends; it is not based on Top ranking.",
"Rewards are granted automatically after the period ends.",
}

View File

@ -0,0 +1,215 @@
package activityapi
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
activityv1 "hyapp.local/api/proto/activity/v1"
"hyapp/pkg/appcode"
"hyapp/services/gateway-service/internal/auth"
)
func TestGetAgencyOpeningStatusReturnsReviewWindowFields(t *testing.T) {
client := &fakeAgencyOpeningClient{statusResp: agencyOpeningApprovedStatusResponse()}
handler := New(Config{AgencyOpening: client})
request := httptest.NewRequest(http.MethodGet, "/api/v1/activities/agency-opening-event", nil)
request = request.WithContext(auth.WithUserID(appcode.WithContext(request.Context(), "lalu"), 42001))
recorder := httptest.NewRecorder()
handler.getAgencyOpeningStatus(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String())
}
if client.lastStatus == nil || client.lastStatus.GetUserId() != 42001 || client.lastStatus.GetMeta().GetAppCode() != "lalu" {
t.Fatalf("status request mismatch: %+v", client.lastStatus)
}
var envelope struct {
Code string `json:"code"`
Data struct {
Joined bool `json:"joined"`
Qualification struct {
OpeningStatus string `json:"opening_status"`
} `json:"qualification"`
Application struct {
Status string `json:"status"`
ApprovedAtMS int64 `json:"approved_at_ms"`
ScoreStartMS int64 `json:"score_start_ms"`
ScoreEndMS int64 `json:"score_end_ms"`
ReviewedByAdminID int64 `json:"reviewed_by_admin_id"`
} `json:"application"`
Rules []string `json:"rules"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil {
t.Fatalf("decode response: %v", err)
}
if envelope.Code != "OK" || !envelope.Data.Joined || envelope.Data.Qualification.OpeningStatus != "Approved" {
t.Fatalf("status envelope mismatch: %+v", envelope)
}
if envelope.Data.Application.Status != "approved" ||
envelope.Data.Application.ApprovedAtMS != 2000 ||
envelope.Data.Application.ScoreStartMS != 2000 ||
envelope.Data.Application.ScoreEndMS != 2000+24*60*60*1000 ||
envelope.Data.Application.ReviewedByAdminID != 7 {
t.Fatalf("application fields mismatch: %+v", envelope.Data.Application)
}
if len(envelope.Data.Rules) < 2 || !strings.Contains(envelope.Data.Rules[1], "24 hours") {
t.Fatalf("rules should explain 24-hour scoring window: %+v", envelope.Data.Rules)
}
}
func TestApplyAgencyOpeningReturnsPendingReviewStatus(t *testing.T) {
client := &fakeAgencyOpeningClient{
applyResp: agencyOpeningApplyResponse(),
statusResp: agencyOpeningAppliedStatusResponse(),
}
handler := New(Config{AgencyOpening: client})
request := httptest.NewRequest(http.MethodPost, "/api/v1/activities/agency-opening-event/apply", strings.NewReader(`{"command_id":"cmd-apply-1"}`))
request = request.WithContext(auth.WithUserID(appcode.WithContext(request.Context(), "lalu"), 42001))
recorder := httptest.NewRecorder()
handler.applyAgencyOpening(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String())
}
if client.lastApply == nil || client.lastApply.GetUserId() != 42001 || client.lastApply.GetCommandId() != "cmd-apply-1" {
t.Fatalf("apply request mismatch: %+v", client.lastApply)
}
var envelope struct {
Code string `json:"code"`
Data struct {
Joined bool `json:"joined"`
Qualification struct {
OpeningStatus string `json:"opening_status"`
} `json:"qualification"`
Application struct {
Status string `json:"status"`
AppliedAtMS int64 `json:"applied_at_ms"`
ScoreStartMS int64 `json:"score_start_ms"`
} `json:"application"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil {
t.Fatalf("decode response: %v", err)
}
if envelope.Code != "OK" || !envelope.Data.Joined || envelope.Data.Qualification.OpeningStatus != "Pending Review" {
t.Fatalf("apply response should show pending review: %+v", envelope)
}
if envelope.Data.Application.Status != "applied" || envelope.Data.Application.AppliedAtMS != 1000 || envelope.Data.Application.ScoreStartMS != 0 {
t.Fatalf("apply application fields mismatch: %+v", envelope.Data.Application)
}
}
type fakeAgencyOpeningClient struct {
statusResp *activityv1.GetAgencyOpeningStatusResponse
applyResp *activityv1.ApplyAgencyOpeningResponse
lastStatus *activityv1.GetAgencyOpeningStatusRequest
lastApply *activityv1.ApplyAgencyOpeningRequest
}
func (f *fakeAgencyOpeningClient) GetAgencyOpeningStatus(_ context.Context, req *activityv1.GetAgencyOpeningStatusRequest) (*activityv1.GetAgencyOpeningStatusResponse, error) {
f.lastStatus = req
if f.statusResp != nil {
return f.statusResp, nil
}
return &activityv1.GetAgencyOpeningStatusResponse{}, nil
}
func (f *fakeAgencyOpeningClient) ApplyAgencyOpening(_ context.Context, req *activityv1.ApplyAgencyOpeningRequest) (*activityv1.ApplyAgencyOpeningResponse, error) {
f.lastApply = req
if f.applyResp != nil {
return f.applyResp, nil
}
return &activityv1.ApplyAgencyOpeningResponse{}, nil
}
func (f *fakeAgencyOpeningClient) ListAgencyOpeningLeaderboard(context.Context, *activityv1.ListAgencyOpeningApplicationsRequest) (*activityv1.ListAgencyOpeningApplicationsResponse, error) {
return &activityv1.ListAgencyOpeningApplicationsResponse{}, nil
}
func agencyOpeningApplyResponse() *activityv1.ApplyAgencyOpeningResponse {
return &activityv1.ApplyAgencyOpeningResponse{
Created: true,
Application: &activityv1.AgencyOpeningApplication{
ApplicationId: "application-1",
CycleId: "cycle-1",
AgencyId: 8001,
AgencyOwnerUserId: 42001,
AgencyName: "Window Agency",
HostCount: 12,
Status: "applied",
AppliedAtMs: 1000,
},
}
}
func agencyOpeningAppliedStatusResponse() *activityv1.GetAgencyOpeningStatusResponse {
resp := agencyOpeningBaseStatusResponse()
resp.Joined = true
resp.Eligible = false
resp.Application = agencyOpeningApplyResponse().GetApplication()
return resp
}
func agencyOpeningApprovedStatusResponse() *activityv1.GetAgencyOpeningStatusResponse {
resp := agencyOpeningBaseStatusResponse()
resp.Joined = true
resp.Eligible = false
resp.Application = &activityv1.AgencyOpeningApplication{
ApplicationId: "application-1",
CycleId: "cycle-1",
AgencyId: 8001,
AgencyOwnerUserId: 42001,
AgencyName: "Window Agency",
HostCount: 12,
Status: "approved",
ScoreCoinAmount: 900,
RewardThresholdCoinSpent: 500,
RewardCoinAmount: 50,
AppliedAtMs: 1000,
ApprovedAtMs: 2000,
ScoreStartMs: 2000,
ScoreEndMs: 2000 + int64((24 * 60 * 60 * 1000)),
ReviewedByAdminId: 7,
}
return resp
}
func agencyOpeningBaseStatusResponse() *activityv1.GetAgencyOpeningStatusResponse {
return &activityv1.GetAgencyOpeningStatusResponse{
ServerTimeMs: 3000,
Enabled: true,
Eligible: true,
Joined: false,
Cycle: &activityv1.AgencyOpeningCycle{
CycleId: "cycle-1",
Title: "Agency Opening",
Status: "active",
StartMs: 1,
EndMs: 2000 + int64((48 * 60 * 60 * 1000)),
MinHostCount: 10,
Rewards: []*activityv1.AgencyOpeningReward{{
RankNo: 1,
ThresholdCoinSpent: 500,
RewardCoinAmount: 50,
}},
},
Agency: &activityv1.AgencyOpeningAgencySnapshot{
AgencyId: 8001,
OwnerUserId: 42001,
Name: "Window Agency",
HostCount: 12,
AgencyCreatedAtMs: 1,
Status: "active",
},
}
}

View File

@ -27,6 +27,7 @@ type Handler struct {
weeklyStar client.WeeklyStarClient
cpWeeklyRank client.CPWeeklyRankClient
agencyOpening client.AgencyOpeningClient
wheel client.WheelClient
}
type Config struct {
@ -47,6 +48,7 @@ type Config struct {
WeeklyStar client.WeeklyStarClient
CPWeeklyRank client.CPWeeklyRankClient
AgencyOpening client.AgencyOpeningClient
Wheel client.WheelClient
}
func New(config Config) *Handler {
@ -68,6 +70,7 @@ func New(config Config) *Handler {
weeklyStar: config.WeeklyStar,
cpWeeklyRank: config.CPWeeklyRank,
agencyOpening: config.AgencyOpening,
wheel: config.Wheel,
}
}
@ -93,6 +96,10 @@ func (h *Handler) TaskHandlers() httproutes.TaskHandlers {
GetAgencyOpeningStatus: h.getAgencyOpeningStatus,
ApplyAgencyOpening: h.applyAgencyOpening,
ListAgencyOpeningLeaderboard: h.listAgencyOpeningLeaderboard,
GetWheelConfig: h.getWheelConfig,
DrawWheel: h.drawWheel,
ListWheelHistory: h.listWheelHistory,
ListWheelHints: h.listWheelHints,
}
}

View File

@ -0,0 +1,349 @@
package activityapi
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
activityv1 "hyapp.local/api/proto/activity/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/idgen"
"hyapp/services/gateway-service/internal/auth"
"hyapp/services/gateway-service/internal/transport/http/httpkit"
)
type wheelDrawBody struct {
Amount int64 `json:"amount"`
CommandID string `json:"command_id"`
RoomID string `json:"roomId"`
Times int32 `json:"times"`
}
type wheelConfigData struct {
WheelID string `json:"wheel_id"`
Enabled bool `json:"enabled"`
DrawPriceCoins int64 `json:"draw_price_coins"`
Rewards []wheelPrizeData `json:"rewards"`
}
type wheelPrizeData struct {
TierID string `json:"tier_id"`
DisplayName string `json:"display_name"`
RewardType string `json:"reward_type"`
RewardID string `json:"reward_id"`
RewardCount int64 `json:"reward_count"`
RewardCoins int64 `json:"reward_coins"`
Enabled bool `json:"enabled"`
MetadataJSON string `json:"metadata_json"`
PreviewURL string `json:"preview_url"`
AnimationURL string `json:"animation_url"`
Value int64 `json:"value"`
}
type wheelDrawData struct {
DrawID string `json:"draw_id"`
DrawIDs []string `json:"draw_ids"`
CommandID string `json:"command_id"`
WheelID string `json:"wheel_id"`
SelectedTierID string `json:"selected_tier_id"`
RewardType string `json:"reward_type"`
RewardID string `json:"reward_id"`
RewardCount int64 `json:"reward_count"`
RewardCoins int64 `json:"reward_coins"`
RewardStatus string `json:"reward_status"`
WalletTransactionID string `json:"wallet_transaction_id"`
CoinBalanceAfter int64 `json:"coin_balance_after"`
MetadataJSON string `json:"metadata_json"`
RewardValue int64 `json:"rewardValue"`
Rewards []wheelPrizeData `json:"rewards"`
}
// getWheelConfig 返回 H5 渲染所需的当前奖池配置;只暴露资源图字段,不暴露后台 RTP 控制窗口。
func (h *Handler) getWheelConfig(writer http.ResponseWriter, request *http.Request) {
if h.wheel == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
resp, err := h.wheel.GetWheelConfig(request.Context(), &activityv1.GetWheelConfigRequest{
Meta: httpkit.ActivityMeta(request),
WheelId: wheelIDFromRequest(request),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, wheelConfigFromProto(resp.GetConfig()))
}
// drawWheel 先完成钱包扣费,再把扣费事实传给 activity-service 抽奖;任何一步失败都直接返回错误,不伪造中奖。
func (h *Handler) drawWheel(writer http.ResponseWriter, request *http.Request) {
if h.wheel == nil || h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
var body wheelDrawBody
if err := httpkit.DecodeJSON(request.Body, &body); err != nil && !errors.Is(err, io.EOF) {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidJSON, "invalid request body")
return
}
wheelID := wheelIDFromRequest(request)
times := body.Times
if times <= 0 {
times = 1
}
configResp, err := h.wheel.GetWheelConfig(request.Context(), &activityv1.GetWheelConfigRequest{
Meta: httpkit.ActivityMeta(request),
WheelId: wheelID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
config := configResp.GetConfig()
if config == nil || !config.GetEnabled() {
httpkit.WriteError(writer, request, http.StatusConflict, httpkit.CodeInvalidArgument, "wheel disabled")
return
}
expectedAmount := config.GetDrawPriceCoins() * int64(times)
if body.Amount > 0 && body.Amount != expectedAmount {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "draw amount mismatch")
return
}
commandID := strings.TrimSpace(body.CommandID)
if commandID == "" {
commandID = idgen.New("wheel")
}
debitResp, err := h.walletClient.DebitWheelDraw(request.Context(), &walletv1.DebitWheelDrawRequest{
CommandId: commandID,
AppCode: appcode.FromContext(request.Context()),
UserId: auth.UserIDFromContext(request.Context()),
WheelId: wheelID,
DrawCount: times,
Amount: expectedAmount,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
drawResp, err := h.wheel.ExecuteWheelDraw(request.Context(), &activityv1.ExecuteWheelDrawRequest{
Wheel: &activityv1.WheelDrawMeta{
Meta: httpkit.ActivityMeta(request),
CommandId: commandID,
WheelId: wheelID,
UserId: auth.UserIDFromContext(request.Context()),
DeviceId: wheelDeviceID(request),
DrawCount: times,
CoinSpent: debitResp.GetCoinSpent(),
PaidAtMs: time.Now().UTC().UnixMilli(),
VisibleRegionId: 0,
},
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
data := wheelDrawFromProto(drawResp.GetResult())
data.WalletTransactionID = debitResp.GetTransactionId()
data.CoinBalanceAfter = debitResp.GetCoinBalanceAfter()
httpkit.WriteOK(writer, request, data)
}
// listWheelHistory 返回当前用户的转盘抽奖记录;读侧复用 activity-service 明细,避免 H5 直接拼后台接口。
func (h *Handler) listWheelHistory(writer http.ResponseWriter, request *http.Request) {
if h.wheel == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
resp, err := h.wheel.ListWheelDraws(request.Context(), &activityv1.ListWheelDrawsRequest{
Meta: httpkit.ActivityMeta(request),
WheelId: wheelIDFromRequest(request),
UserId: auth.UserIDFromContext(request.Context()),
Page: int32(queryInt(request, "pageNo", 1)),
PageSize: int32(queryInt(request, "pageSize", 20)),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
records := make([]map[string]any, 0, len(resp.GetDraws()))
for _, item := range resp.GetDraws() {
draw := wheelDrawFromProto(item)
records = append(records, map[string]any{
"createdTime": item.GetCreatedAtMs(),
"drawTimes": len(item.GetDrawIds()),
"rewardValue": draw.RewardValue,
"rewards": draw.Rewards,
})
}
httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
}
// listWheelHints 返回最近中奖提示;昵称不在 activity-service 明细里,这里用用户 ID 占位,后续可接 user profile 批量补齐。
func (h *Handler) listWheelHints(writer http.ResponseWriter, request *http.Request) {
if h.wheel == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
resp, err := h.wheel.ListWheelDraws(request.Context(), &activityv1.ListWheelDrawsRequest{
Meta: httpkit.ActivityMeta(request),
WheelId: wheelIDFromRequest(request),
Status: "granted",
Page: 1,
PageSize: 20,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
values := make([]map[string]any, 0, len(resp.GetDraws()))
for _, item := range resp.GetDraws() {
reward := wheelPrizeFromDraw(item)
values = append(values, map[string]any{
"avatar": reward.PreviewURL,
"nickname": fmt.Sprintf("Winner %d", len(values)+1),
"value": reward.DisplayName,
})
}
httpkit.WriteOK(writer, request, map[string]any{"values": values})
}
func wheelIDFromRequest(request *http.Request) string {
value := strings.TrimSpace(request.PathValue("wheel_id"))
if value == "" {
return "classic"
}
return value
}
func wheelDeviceID(request *http.Request) string {
for _, value := range []string{
httpkit.FirstQueryOrHeader(request, "device_id", "X-Device-ID", "X-Hyapp-Device-ID"),
auth.SessionIDFromContext(request.Context()),
} {
if strings.TrimSpace(value) != "" {
return value
}
}
return "h5"
}
func wheelConfigFromProto(config *activityv1.WheelRuleConfig) wheelConfigData {
if config == nil {
return wheelConfigData{Rewards: []wheelPrizeData{}}
}
rewards := make([]wheelPrizeData, 0, len(config.GetTiers()))
for _, tier := range config.GetTiers() {
rewards = append(rewards, wheelPrizeFromTier(tier))
}
return wheelConfigData{
WheelID: config.GetWheelId(),
Enabled: config.GetEnabled(),
DrawPriceCoins: config.GetDrawPriceCoins(),
Rewards: rewards,
}
}
func wheelPrizeFromTier(tier *activityv1.WheelPrizeTier) wheelPrizeData {
if tier == nil {
return wheelPrizeData{}
}
previewURL, animationURL := wheelAssetURLs(tier.GetRewardType(), tier.GetMetadataJson())
return wheelPrizeData{
TierID: tier.GetTierId(),
DisplayName: tier.GetDisplayName(),
RewardType: tier.GetRewardType(),
RewardID: tier.GetRewardId(),
RewardCount: tier.GetRewardCount(),
RewardCoins: tier.GetRewardCoins(),
Enabled: tier.GetEnabled(),
MetadataJSON: tier.GetMetadataJson(),
PreviewURL: previewURL,
AnimationURL: animationURL,
Value: wheelPrizeDisplayValue(tier.GetRewardCoins(), tier.GetRtpValueCoins(), tier.GetRewardCount()),
}
}
func wheelDrawFromProto(item *activityv1.WheelDrawResult) wheelDrawData {
if item == nil {
return wheelDrawData{Rewards: []wheelPrizeData{}}
}
reward := wheelPrizeFromDraw(item)
return wheelDrawData{
DrawID: item.GetDrawId(),
DrawIDs: item.GetDrawIds(),
CommandID: item.GetCommandId(),
WheelID: item.GetWheelId(),
SelectedTierID: item.GetSelectedTierId(),
RewardType: item.GetRewardType(),
RewardID: item.GetRewardId(),
RewardCount: item.GetRewardCount(),
RewardCoins: item.GetRewardCoins(),
RewardStatus: item.GetRewardStatus(),
WalletTransactionID: item.GetWalletTransactionId(),
CoinBalanceAfter: item.GetCoinBalanceAfter(),
MetadataJSON: item.GetMetadataJson(),
RewardValue: reward.Value,
Rewards: []wheelPrizeData{reward},
}
}
func wheelPrizeFromDraw(item *activityv1.WheelDrawResult) wheelPrizeData {
if item == nil {
return wheelPrizeData{}
}
previewURL, animationURL := wheelAssetURLs(item.GetRewardType(), item.GetMetadataJson())
return wheelPrizeData{
TierID: item.GetSelectedTierId(),
DisplayName: item.GetSelectedTierId(),
RewardType: item.GetRewardType(),
RewardID: item.GetRewardId(),
RewardCount: item.GetRewardCount(),
RewardCoins: item.GetRewardCoins(),
MetadataJSON: item.GetMetadataJson(),
PreviewURL: previewURL,
AnimationURL: animationURL,
Value: wheelPrizeDisplayValue(item.GetRewardCoins(), item.GetRtpValueCoins(), item.GetRewardCount()),
}
}
func wheelPrizeDisplayValue(rewardCoins int64, internalValueCoins int64, rewardCount int64) int64 {
// H5 只需要一个展示值,不能把后台 RTP 字段原样暴露出去;金币奖品展示金币数,礼物可展示内部估值。
// 道具和装扮不计入 RTP 时内部估值会是 0这时回退奖励数量避免页面拿到 value=0 后看起来像奖品没有价值。
if rewardCoins > 0 {
return rewardCoins
}
if internalValueCoins > 0 {
return internalValueCoins
}
if rewardCount > 0 {
return rewardCount
}
return 1
}
func wheelAssetURLs(rewardType string, metadataJSON string) (string, string) {
var metadata map[string]any
if err := json.NewDecoder(strings.NewReader(strings.TrimSpace(metadataJSON))).Decode(&metadata); err != nil && err != io.EOF {
return "", ""
}
preview := firstMetadataString(metadata, "preview_url", "previewUrl", "asset_url", "assetUrl")
if strings.EqualFold(strings.TrimSpace(rewardType), "coin") {
return preview, ""
}
animation := firstMetadataString(metadata, "animation_url", "animationUrl")
return preview, animation
}
func firstMetadataString(metadata map[string]any, keys ...string) string {
for _, key := range keys {
if value, ok := metadata[key].(string); ok && strings.TrimSpace(value) != "" {
return value
}
}
return ""
}

Some files were not shown because too many files have changed in this diff Show More